Merge remote-tracking branch 'origin/refactor_remote_operation_to_create_folder'...
[pub/Android/ownCloud.git] / src / com / owncloud / android / authentication / AuthenticatorActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2,
7 * as published by the Free Software Foundation.
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.authentication;
20
21 import android.accounts.Account;
22 import android.accounts.AccountManager;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.app.ProgressDialog;
26 import android.content.ContentResolver;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.SharedPreferences;
30 import android.graphics.Rect;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.preference.PreferenceManager;
36 import android.support.v4.app.Fragment;
37 import android.text.Editable;
38 import android.text.InputType;
39 import android.text.TextWatcher;
40 import android.view.KeyEvent;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.View.OnFocusChangeListener;
44 import android.view.View.OnTouchListener;
45 import android.view.Window;
46 import android.view.inputmethod.EditorInfo;
47 import android.widget.Button;
48 import android.widget.CheckBox;
49 import android.widget.EditText;
50 import android.widget.TextView;
51 import android.widget.TextView.OnEditorActionListener;
52
53 import com.actionbarsherlock.app.SherlockDialogFragment;
54 import com.owncloud.android.Log_OC;
55 import com.owncloud.android.MainApp;
56 import com.owncloud.android.R;
57 import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener;
58 import com.owncloud.android.oc_framework.accounts.AccountTypeUtils;
59 import com.owncloud.android.oc_framework.accounts.OwnCloudAccount;
60 import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory;
61 import com.owncloud.android.oc_framework.network.webdav.WebdavClient;
62 import com.owncloud.android.operations.ExistenceCheckOperation;
63 import com.owncloud.android.operations.OAuth2GetAccessToken;
64 import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener;
65 import com.owncloud.android.operations.OwnCloudServerCheckOperation;
66 import com.owncloud.android.oc_framework.operations.RemoteOperation;
67 import com.owncloud.android.oc_framework.operations.RemoteOperationResult;
68 import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode;
69 import com.owncloud.android.ui.dialog.SamlWebViewDialog;
70 import com.owncloud.android.ui.dialog.SslValidatorDialog;
71 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
72 import com.owncloud.android.oc_framework.utils.OwnCloudVersion;
73
74
75 /**
76 * This Activity is used to add an ownCloud account to the App
77 *
78 * @author Bartek Przybylski
79 * @author David A. Velasco
80 */
81 public class AuthenticatorActivity extends AccountAuthenticatorActivity
82 implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener, OnEditorActionListener, SsoWebViewClientListener{
83
84 private static final String TAG = AuthenticatorActivity.class.getSimpleName();
85
86 public static final String EXTRA_ACCOUNT = "ACCOUNT";
87 public static final String EXTRA_USER_NAME = "USER_NAME";
88 public static final String EXTRA_HOST_NAME = "HOST_NAME";
89 public static final String EXTRA_ACTION = "ACTION";
90 public static final String EXTRA_ENFORCED_UPDATE = "ENFORCE_UPDATE";
91
92 private static final String KEY_AUTH_MESSAGE_VISIBILITY = "AUTH_MESSAGE_VISIBILITY";
93 private static final String KEY_AUTH_MESSAGE_TEXT = "AUTH_MESSAGE_TEXT";
94 private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT";
95 private static final String KEY_OC_VERSION = "OC_VERSION";
96 private static final String KEY_ACCOUNT = "ACCOUNT";
97 private static final String KEY_SERVER_VALID = "SERVER_VALID";
98 private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED";
99 private static final String KEY_SERVER_CHECK_IN_PROGRESS = "SERVER_CHECK_IN_PROGRESS";
100 private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT";
101 private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON";
102 private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN";
103 private static final String KEY_PASSWORD_VISIBLE = "PASSWORD_VISIBLE";
104 private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT";
105 private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON";
106 private static final String KEY_REFRESH_BUTTON_ENABLED = "KEY_REFRESH_BUTTON_ENABLED";
107
108 private static final String KEY_OC_USERNAME_EQUALS = "oc_username=";
109
110 private static final String AUTH_ON = "on";
111 private static final String AUTH_OFF = "off";
112 private static final String AUTH_OPTIONAL = "optional";
113
114 private static final int DIALOG_LOGIN_PROGRESS = 0;
115 private static final int DIALOG_SSL_VALIDATOR = 1;
116 private static final int DIALOG_CERT_NOT_SAVED = 2;
117 private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3;
118
119 public static final byte ACTION_CREATE = 0;
120 public static final byte ACTION_UPDATE_TOKEN = 1;
121
122 private static final String TAG_SAML_DIALOG = "samlWebViewDialog";
123
124 private String mHostBaseUrl;
125 private OwnCloudVersion mDiscoveredVersion;
126
127 private String mAuthMessageText;
128 private int mAuthMessageVisibility, mServerStatusText, mServerStatusIcon;
129 private boolean mServerIsChecked, mServerIsValid, mIsSslConn;
130 private int mAuthStatusText, mAuthStatusIcon;
131 private TextView mAuthStatusLayout;
132
133 private final Handler mHandler = new Handler();
134 private Thread mOperationThread;
135 private OwnCloudServerCheckOperation mOcServerChkOperation;
136 private ExistenceCheckOperation mAuthCheckOperation;
137 private RemoteOperationResult mLastSslUntrustedServerResult;
138
139 private Uri mNewCapturedUriFromOAuth2Redirection;
140
141 private AccountManager mAccountMgr;
142 private boolean mJustCreated;
143 private byte mAction;
144 private Account mAccount;
145
146 private TextView mAuthMessage;
147
148 private EditText mHostUrlInput;
149 private boolean mHostUrlInputEnabled;
150 private View mRefreshButton;
151
152 private String mAuthTokenType;
153
154 private EditText mUsernameInput;
155 private EditText mPasswordInput;
156
157 private CheckBox mOAuth2Check;
158
159 private TextView mOAuthAuthEndpointText;
160 private TextView mOAuthTokenEndpointText;
161
162 private SamlWebViewDialog mSamlDialog;
163
164 private View mOkButton;
165
166 private String mAuthToken;
167
168 private boolean mResumed; // Control if activity is resumed
169
170
171 /**
172 * {@inheritDoc}
173 *
174 * IMPORTANT ENTRY POINT 1: activity is shown to the user
175 */
176 @Override
177 protected void onCreate(Bundle savedInstanceState) {
178 super.onCreate(savedInstanceState);
179 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
180
181 /// set view and get references to view elements
182 setContentView(R.layout.account_setup);
183 mAuthMessage = (TextView) findViewById(R.id.auth_message);
184 mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput);
185 mHostUrlInput.setText(getString(R.string.server_url)); // valid although R.string.server_url is an empty string
186 mUsernameInput = (EditText) findViewById(R.id.account_username);
187 mPasswordInput = (EditText) findViewById(R.id.account_password);
188 mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1);
189 mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2);
190 mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
191 mOkButton = findViewById(R.id.buttonOK);
192 mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text);
193
194 /// set Host Url Input Enabled
195 mHostUrlInputEnabled = getResources().getBoolean(R.bool.show_server_url_input);
196
197
198 /// complete label for 'register account' button
199 Button b = (Button) findViewById(R.id.account_register);
200 if (b != null) {
201 b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));
202 }
203
204 /// initialization
205 mAccountMgr = AccountManager.get(this);
206 mNewCapturedUriFromOAuth2Redirection = null;
207 mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE);
208 mAccount = null;
209 mHostBaseUrl = "";
210 boolean refreshButtonEnabled = false;
211
212 // URL input configuration applied
213 if (!mHostUrlInputEnabled)
214 {
215 findViewById(R.id.hostUrlFrame).setVisibility(View.GONE);
216 mRefreshButton = findViewById(R.id.centeredRefreshButton);
217
218 } else {
219 mRefreshButton = findViewById(R.id.embeddedRefreshButton);
220 }
221
222 if (savedInstanceState == null) {
223 mResumed = false;
224 /// connection state and info
225 mAuthMessageVisibility = View.GONE;
226 mServerStatusText = mServerStatusIcon = 0;
227 mServerIsValid = false;
228 mServerIsChecked = false;
229 mIsSslConn = false;
230 mAuthStatusText = mAuthStatusIcon = 0;
231
232 /// retrieve extras from intent
233 mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
234 if (mAccount != null) {
235 String ocVersion = mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_VERSION);
236 if (ocVersion != null) {
237 mDiscoveredVersion = new OwnCloudVersion(ocVersion);
238 }
239 mHostBaseUrl = normalizeUrl(mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_BASE_URL));
240 mHostUrlInput.setText(mHostBaseUrl);
241 String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@'));
242 mUsernameInput.setText(userName);
243 }
244 initAuthorizationMethod(); // checks intent and setup.xml to determine mCurrentAuthorizationMethod
245 mJustCreated = true;
246
247 if (mAction == ACTION_UPDATE_TOKEN || !mHostUrlInputEnabled) {
248 checkOcServer();
249 }
250
251 } else {
252 mResumed = true;
253 /// connection state and info
254 mAuthMessageVisibility = savedInstanceState.getInt(KEY_AUTH_MESSAGE_VISIBILITY);
255 mAuthMessageText = savedInstanceState.getString(KEY_AUTH_MESSAGE_TEXT);
256 mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID);
257 mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED);
258 mServerStatusText = savedInstanceState.getInt(KEY_SERVER_STATUS_TEXT);
259 mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON);
260 mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN);
261 mAuthStatusText = savedInstanceState.getInt(KEY_AUTH_STATUS_TEXT);
262 mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON);
263 if (savedInstanceState.getBoolean(KEY_PASSWORD_VISIBLE, false)) {
264 showPassword();
265 }
266
267 /// server data
268 String ocVersion = savedInstanceState.getString(KEY_OC_VERSION);
269 if (ocVersion != null) {
270 mDiscoveredVersion = new OwnCloudVersion(ocVersion);
271 }
272 mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT);
273
274 // account data, if updating
275 mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT);
276 mAuthTokenType = savedInstanceState.getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
277 if (mAuthTokenType == null) {
278 mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType());
279
280 }
281
282 // check if server check was interrupted by a configuration change
283 if (savedInstanceState.getBoolean(KEY_SERVER_CHECK_IN_PROGRESS, false)) {
284 checkOcServer();
285 }
286
287 // refresh button enabled
288 refreshButtonEnabled = savedInstanceState.getBoolean(KEY_REFRESH_BUTTON_ENABLED);
289
290
291 }
292
293 if (mAuthMessageVisibility== View.VISIBLE) {
294 showAuthMessage(mAuthMessageText);
295 }
296 else {
297 hideAuthMessage();
298 }
299 adaptViewAccordingToAuthenticationMethod();
300 showServerStatus();
301 showAuthStatus();
302
303 if (mAction == ACTION_UPDATE_TOKEN) {
304 /// lock things that should not change
305 mHostUrlInput.setEnabled(false);
306 mHostUrlInput.setFocusable(false);
307 mUsernameInput.setEnabled(false);
308 mUsernameInput.setFocusable(false);
309 mOAuth2Check.setVisibility(View.GONE);
310 }
311
312 //if (mServerIsChecked && !mServerIsValid && mRefreshButtonEnabled) showRefreshButton();
313 if (mServerIsChecked && !mServerIsValid && refreshButtonEnabled) showRefreshButton();
314 mOkButton.setEnabled(mServerIsValid); // state not automatically recovered in configuration changes
315
316 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) ||
317 !AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) {
318 mOAuth2Check.setVisibility(View.GONE);
319 }
320
321 mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside)
322
323 /// bind view elements to listeners and other friends
324 mHostUrlInput.setOnFocusChangeListener(this);
325 mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
326 mHostUrlInput.setOnEditorActionListener(this);
327 mHostUrlInput.addTextChangedListener(new TextWatcher() {
328
329 @Override
330 public void afterTextChanged(Editable s) {
331 if (!mHostBaseUrl.equals(normalizeUrl(mHostUrlInput.getText().toString()))) {
332 mOkButton.setEnabled(false);
333 }
334 }
335
336 @Override
337 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
338 }
339
340 @Override
341 public void onTextChanged(CharSequence s, int start, int before, int count) {
342 if (!mResumed) {
343 mAuthStatusIcon = 0;
344 mAuthStatusText = 0;
345 showAuthStatus();
346 }
347 mResumed = false;
348 }
349 });
350
351 mPasswordInput.setOnFocusChangeListener(this);
352 mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
353 mPasswordInput.setOnEditorActionListener(this);
354 mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() {
355 @Override
356 public boolean onDrawableTouch(final MotionEvent event) {
357 if (event.getAction() == MotionEvent.ACTION_UP) {
358 AuthenticatorActivity.this.onViewPasswordClick();
359 }
360 return true;
361 }
362 });
363
364 findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() {
365 @Override
366 public boolean onTouch(View view, MotionEvent event) {
367 if (event.getAction() == MotionEvent.ACTION_DOWN) {
368 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) &&
369 mHostUrlInput.hasFocus()) {
370 checkOcServer();
371 }
372 }
373 return false;
374 }
375 });
376 }
377
378
379
380 private void initAuthorizationMethod() {
381 boolean oAuthRequired = false;
382 boolean samlWebSsoRequired = false;
383
384 mAuthTokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
385 mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
386
387 // TODO could be a good moment to validate the received token type, if not null
388
389 if (mAuthTokenType == null) {
390 if (mAccount != null) {
391 /// same authentication method than the one used to create the account to update
392 oAuthRequired = (mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_OAUTH2) != null);
393 samlWebSsoRequired = (mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null);
394
395 } else {
396 /// use the one set in setup.xml
397 oAuthRequired = AUTH_ON.equals(getString(R.string.auth_method_oauth2));
398 samlWebSsoRequired = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
399 }
400 if (oAuthRequired) {
401 mAuthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType());
402 } else if (samlWebSsoRequired) {
403 mAuthTokenType = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType());
404 } else {
405 mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType());
406 }
407 }
408
409 if (mAccount != null) {
410 String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@'));
411 mUsernameInput.setText(userName);
412 }
413
414 mOAuth2Check.setChecked(AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType));
415
416 }
417
418 /**
419 * Saves relevant state before {@link #onPause()}
420 *
421 * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the
422 * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()}
423 *
424 * See {@link #loadSavedInstanceState(Bundle)}
425 */
426 @Override
427 protected void onSaveInstanceState(Bundle outState) {
428 super.onSaveInstanceState(outState);
429
430 /// connection state and info
431 outState.putInt(KEY_AUTH_MESSAGE_VISIBILITY, mAuthMessage.getVisibility());
432 outState.putString(KEY_AUTH_MESSAGE_TEXT, mAuthMessage.getText().toString());
433 outState.putInt(KEY_SERVER_STATUS_TEXT, mServerStatusText);
434 outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon);
435 outState.putBoolean(KEY_SERVER_VALID, mServerIsValid);
436 outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked);
437 outState.putBoolean(KEY_SERVER_CHECK_IN_PROGRESS, (!mServerIsValid && mOcServerChkOperation != null));
438 outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn);
439 outState.putBoolean(KEY_PASSWORD_VISIBLE, isPasswordVisible());
440 outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon);
441 outState.putInt(KEY_AUTH_STATUS_TEXT, mAuthStatusText);
442
443 /// server data
444 if (mDiscoveredVersion != null) {
445 outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString());
446 }
447 outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl);
448
449 /// account data, if updating
450 if (mAccount != null) {
451 outState.putParcelable(KEY_ACCOUNT, mAccount);
452 }
453 outState.putString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE, mAuthTokenType);
454
455 // refresh button enabled
456 outState.putBoolean(KEY_REFRESH_BUTTON_ENABLED, (mRefreshButton.getVisibility() == View.VISIBLE));
457
458
459 }
460
461
462 /**
463 * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request
464 * is caught here.
465 *
466 * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the
467 * AndroidManifest.xml file.
468 */
469 @Override
470 protected void onNewIntent (Intent intent) {
471 Log_OC.d(TAG, "onNewIntent()");
472 Uri data = intent.getData();
473 if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) {
474 mNewCapturedUriFromOAuth2Redirection = data;
475 }
476 }
477
478
479 /**
480 * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and
481 * deferred in {@link #onNewIntent(Intent)}, is processed here.
482 */
483 @Override
484 protected void onResume() {
485 super.onResume();
486 if (mAction == ACTION_UPDATE_TOKEN && mJustCreated && getIntent().getBooleanExtra(EXTRA_ENFORCED_UPDATE, false)) {
487 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) {
488 //Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show();
489 showAuthMessage(getString(R.string.auth_expired_oauth_token_toast));
490 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) {
491 //Toast.makeText(this, R.string.auth_expired_saml_sso_token_toast, Toast.LENGTH_LONG).show();
492 showAuthMessage(getString(R.string.auth_expired_saml_sso_token_toast));
493 } else {
494 //Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show();
495 showAuthMessage(getString(R.string.auth_expired_basic_auth_toast));
496 }
497 }
498
499 if (mNewCapturedUriFromOAuth2Redirection != null) {
500 getOAuth2AccessTokenFromCapturedRedirection();
501 }
502
503 mJustCreated = false;
504
505 }
506
507
508 /**
509 * Parses the redirection with the response to the GET AUTHORIZATION request to the
510 * oAuth server and requests for the access token (GET ACCESS TOKEN)
511 */
512 private void getOAuth2AccessTokenFromCapturedRedirection() {
513 /// Parse data from OAuth redirection
514 String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();
515 mNewCapturedUriFromOAuth2Redirection = null;
516
517 /// Showing the dialog with instructions for the user.
518 showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS);
519
520 /// GET ACCESS TOKEN to the oAuth server
521 RemoteOperation operation = new OAuth2GetAccessToken( getString(R.string.oauth2_client_id),
522 getString(R.string.oauth2_redirect_uri),
523 getString(R.string.oauth2_grant_type),
524 queryParameters);
525 //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext());
526 WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext(), true);
527 operation.execute(client, this, mHandler);
528 }
529
530
531
532 /**
533 * Handles the change of focus on the text inputs for the server URL and the password
534 */
535 public void onFocusChange(View view, boolean hasFocus) {
536 if (view.getId() == R.id.hostUrlInput) {
537 if (!hasFocus) {
538 onUrlInputFocusLost((TextView) view);
539 }
540 else {
541 hideRefreshButton();
542 }
543
544 } else if (view.getId() == R.id.account_password) {
545 onPasswordFocusChanged((TextView) view, hasFocus);
546 }
547 }
548
549
550 /**
551 * Handles changes in focus on the text input for the server URL.
552 *
553 * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to
554 * other field. The operation to check the existence of the server in the entered URL is
555 * started.
556 *
557 * When hasFocus: user 'comes back' to write again the server URL.
558 *
559 * @param hostInput TextView with the URL input field receiving the change of focus.
560 */
561 private void onUrlInputFocusLost(TextView hostInput) {
562 if (!mHostBaseUrl.equals(normalizeUrl(mHostUrlInput.getText().toString()))) {
563 checkOcServer();
564 } else {
565 mOkButton.setEnabled(mServerIsValid);
566 if (!mServerIsValid) {
567 showRefreshButton();
568 }
569 }
570 }
571
572
573 private void checkOcServer() {
574 String uri = trimUrlWebdav(mHostUrlInput.getText().toString().trim());
575
576 if (!mHostUrlInputEnabled){
577 uri = getString(R.string.server_url);
578 }
579
580 mServerIsValid = false;
581 mServerIsChecked = false;
582 mOkButton.setEnabled(false);
583 mDiscoveredVersion = null;
584 hideRefreshButton();
585 if (uri.length() != 0) {
586 mServerStatusText = R.string.auth_testing_connection;
587 mServerStatusIcon = R.drawable.progress_small;
588 showServerStatus();
589 mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this);
590 WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(uri), this, true);
591 mOperationThread = mOcServerChkOperation.execute(client, this, mHandler);
592 } else {
593 mServerStatusText = 0;
594 mServerStatusIcon = 0;
595 showServerStatus();
596 }
597 }
598
599
600 /**
601 * Handles changes in focus on the text input for the password (basic authorization).
602 *
603 * When (hasFocus), the button to toggle password visibility is shown.
604 *
605 * When (!hasFocus), the button is made invisible and the password is hidden.
606 *
607 * @param passwordInput TextView with the password input field receiving the change of focus.
608 * @param hasFocus 'True' if focus is received, 'false' if is lost
609 */
610 private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) {
611 if (hasFocus) {
612 showViewPasswordButton();
613 } else {
614 hidePassword();
615 hidePasswordButton();
616 }
617 }
618
619
620 private void showViewPasswordButton() {
621 //int drawable = android.R.drawable.ic_menu_view;
622 int drawable = R.drawable.ic_view;
623 if (isPasswordVisible()) {
624 //drawable = android.R.drawable.ic_secure;
625 drawable = R.drawable.ic_hide;
626 }
627 mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0);
628 }
629
630 private boolean isPasswordVisible() {
631 return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
632 }
633
634 private void hidePasswordButton() {
635 mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
636 }
637
638 private void showPassword() {
639 mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
640 showViewPasswordButton();
641 }
642
643 private void hidePassword() {
644 mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
645 showViewPasswordButton();
646 }
647
648
649 /**
650 * Cancels the authenticator activity
651 *
652 * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation
653 *
654 * This method is bound in the layout/acceoun_setup.xml resource file.
655 *
656 * @param view Cancel button
657 */
658 public void onCancelClick(View view) {
659 setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator (debugging)
660 finish();
661 }
662
663
664
665 /**
666 * Checks the credentials of the user in the root of the ownCloud server
667 * before creating a new local account.
668 *
669 * For basic authorization, a check of existence of the root folder is
670 * performed.
671 *
672 * For OAuth, starts the flow to get an access token; the credentials test
673 * is postponed until it is available.
674 *
675 * IMPORTANT ENTRY POINT 4
676 *
677 * @param view OK button
678 */
679 public void onOkClick(View view) {
680 // this check should be unnecessary
681 if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) {
682 mServerStatusIcon = R.drawable.common_error;
683 mServerStatusText = R.string.auth_wtf_reenter_URL;
684 showServerStatus();
685 mOkButton.setEnabled(false);
686 Log_OC.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!");
687 return;
688 }
689
690 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) {
691 startOauthorization();
692 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) {
693 startSamlBasedFederatedSingleSignOnAuthorization();
694 } else {
695 checkBasicAuthorization();
696 }
697 }
698
699
700 /**
701 * Tests the credentials entered by the user performing a check of existence on
702 * the root folder of the ownCloud server.
703 */
704 private void checkBasicAuthorization() {
705 /// get the path to the root folder through WebDAV from the version server
706 String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType);
707
708 /// get basic credentials entered by user
709 String username = mUsernameInput.getText().toString();
710 String password = mPasswordInput.getText().toString();
711
712 /// be gentle with the user
713 showDialog(DIALOG_LOGIN_PROGRESS);
714
715 /// test credentials accessing the root folder
716 mAuthCheckOperation = new ExistenceCheckOperation("", this, false);
717 WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true);
718 client.setBasicCredentials(username, password);
719 mOperationThread = mAuthCheckOperation.execute(client, this, mHandler);
720 }
721
722
723 /**
724 * Starts the OAuth 'grant type' flow to get an access token, with
725 * a GET AUTHORIZATION request to the BUILT-IN authorization server.
726 */
727 private void startOauthorization() {
728 // be gentle with the user
729 mAuthStatusIcon = R.drawable.progress_small;
730 mAuthStatusText = R.string.oauth_login_connection;
731 showAuthStatus();
732
733
734 // GET AUTHORIZATION request
735 //Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth));
736 Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim());
737 Uri.Builder uriBuilder = uri.buildUpon();
738 uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type));
739 uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri));
740 uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id));
741 uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope));
742 //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant);
743 uri = uriBuilder.build();
744 Log_OC.d(TAG, "Starting browser to view " + uri.toString());
745 Intent i = new Intent(Intent.ACTION_VIEW, uri);
746 startActivity(i);
747 }
748
749
750 /**
751 * Starts the Web Single Sign On flow to get access to the root folder
752 * in the server.
753 */
754 private void startSamlBasedFederatedSingleSignOnAuthorization() {
755 // be gentle with the user
756 mAuthStatusIcon = R.drawable.progress_small;
757 mAuthStatusText = R.string.auth_connecting_auth_server;
758 showAuthStatus();
759 showDialog(DIALOG_LOGIN_PROGRESS);
760
761 /// get the path to the root folder through WebDAV from the version server
762 String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType);
763
764 /// test credentials accessing the root folder
765 mAuthCheckOperation = new ExistenceCheckOperation("", this, false);
766 WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, false);
767 mOperationThread = mAuthCheckOperation.execute(client, this, mHandler);
768
769 }
770
771 /**
772 * Callback method invoked when a RemoteOperation executed by this Activity finishes.
773 *
774 * Dispatches the operation flow to the right method.
775 */
776 @Override
777 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
778
779 if (operation instanceof OwnCloudServerCheckOperation) {
780 onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result);
781
782 } else if (operation instanceof OAuth2GetAccessToken) {
783 onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result);
784
785 } else if (operation instanceof ExistenceCheckOperation) {
786 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) {
787 onSamlBasedFederatedSingleSignOnAuthorizationStart(operation, result);
788
789 } else {
790 onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result);
791 }
792 }
793 }
794
795
796 private void onSamlBasedFederatedSingleSignOnAuthorizationStart(RemoteOperation operation, RemoteOperationResult result) {
797 try {
798 dismissDialog(DIALOG_LOGIN_PROGRESS);
799 } catch (IllegalArgumentException e) {
800 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
801 }
802
803 //if (result.isTemporalRedirection() && result.isIdPRedirection()) {
804 if (result.isIdPRedirection()) {
805 String url = result.getRedirectedLocation();
806 String targetUrl = mHostBaseUrl + AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType);
807
808 // Show dialog
809 mSamlDialog = SamlWebViewDialog.newInstance(url, targetUrl);
810 mSamlDialog.show(getSupportFragmentManager(), TAG_SAML_DIALOG);
811
812 mAuthStatusIcon = 0;
813 mAuthStatusText = 0;
814
815 } else {
816 mAuthStatusIcon = R.drawable.common_error;
817 mAuthStatusText = R.string.auth_unsupported_auth_method;
818
819 }
820 showAuthStatus();
821 }
822
823
824 /**
825 * Processes the result of the server check performed when the user finishes the enter of the
826 * server URL.
827 *
828 * @param operation Server check performed.
829 * @param result Result of the check.
830 */
831 private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) {
832 if (operation.equals(mOcServerChkOperation)) {
833 /// save result state
834 mServerIsChecked = true;
835 mServerIsValid = result.isSuccess();
836 mIsSslConn = (result.getCode() == ResultCode.OK_SSL);
837 mOcServerChkOperation = null;
838
839 /// update status icon and text
840 if (mServerIsValid) {
841 hideRefreshButton();
842 } else {
843 showRefreshButton();
844 }
845 updateServerStatusIconAndText(result);
846 showServerStatus();
847
848 /// very special case (TODO: move to a common place for all the remote operations)
849 if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
850 mLastSslUntrustedServerResult = result;
851 showDialog(DIALOG_SSL_VALIDATOR);
852 }
853
854 /// retrieve discovered version and normalize server URL
855 mDiscoveredVersion = operation.getDiscoveredVersion();
856 mHostBaseUrl = normalizeUrl(mHostUrlInput.getText().toString());
857
858 /// allow or not the user try to access the server
859 mOkButton.setEnabled(mServerIsValid);
860
861 } // else nothing ; only the last check operation is considered;
862 // multiple can be triggered if the user amends a URL before a previous check can be triggered
863 }
864
865
866 private String normalizeUrl(String url) {
867 if (url != null && url.length() > 0) {
868 url = url.trim();
869 if (!url.toLowerCase().startsWith("http://") &&
870 !url.toLowerCase().startsWith("https://")) {
871 if (mIsSslConn) {
872 url = "https://" + url;
873 } else {
874 url = "http://" + url;
875 }
876 }
877
878 // OC-208: Add suffix remote.php/webdav to normalize (OC-34)
879 url = trimUrlWebdav(url);
880
881 if (url.endsWith("/")) {
882 url = url.substring(0, url.length() - 1);
883 }
884
885 }
886 return (url != null ? url : "");
887 }
888
889
890 private String trimUrlWebdav(String url){
891 if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_4_0)){
892 url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_4_0.length());
893 } else if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_2_0)){
894 url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_2_0.length());
895 } else if (url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_1_2)){
896 url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_1_2.length());
897 }
898 return (url != null ? url : "");
899 }
900
901
902 /**
903 * Chooses the right icon and text to show to the user for the received operation result.
904 *
905 * @param result Result of a remote operation performed in this activity
906 */
907 private void updateServerStatusIconAndText(RemoteOperationResult result) {
908 mServerStatusIcon = R.drawable.common_error; // the most common case in the switch below
909
910 switch (result.getCode()) {
911 case OK_SSL:
912 mServerStatusIcon = android.R.drawable.ic_secure;
913 mServerStatusText = R.string.auth_secure_connection;
914 break;
915
916 case OK_NO_SSL:
917 case OK:
918 if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) {
919 mServerStatusText = R.string.auth_connection_established;
920 mServerStatusIcon = R.drawable.ic_ok;
921 } else {
922 mServerStatusText = R.string.auth_nossl_plain_ok_title;
923 mServerStatusIcon = android.R.drawable.ic_partial_secure;
924 }
925 break;
926
927 case NO_NETWORK_CONNECTION:
928 mServerStatusIcon = R.drawable.no_network;
929 mServerStatusText = R.string.auth_no_net_conn_title;
930 break;
931
932 case SSL_RECOVERABLE_PEER_UNVERIFIED:
933 mServerStatusText = R.string.auth_ssl_unverified_server_title;
934 break;
935 case BAD_OC_VERSION:
936 mServerStatusText = R.string.auth_bad_oc_version_title;
937 break;
938 case WRONG_CONNECTION:
939 mServerStatusText = R.string.auth_wrong_connection_title;
940 break;
941 case TIMEOUT:
942 mServerStatusText = R.string.auth_timeout_title;
943 break;
944 case INCORRECT_ADDRESS:
945 mServerStatusText = R.string.auth_incorrect_address_title;
946 break;
947 case SSL_ERROR:
948 mServerStatusText = R.string.auth_ssl_general_error_title;
949 break;
950 case UNAUTHORIZED:
951 mServerStatusText = R.string.auth_unauthorized;
952 break;
953 case HOST_NOT_AVAILABLE:
954 mServerStatusText = R.string.auth_unknown_host_title;
955 break;
956 case INSTANCE_NOT_CONFIGURED:
957 mServerStatusText = R.string.auth_not_configured_title;
958 break;
959 case FILE_NOT_FOUND:
960 mServerStatusText = R.string.auth_incorrect_path_title;
961 break;
962 case OAUTH2_ERROR:
963 mServerStatusText = R.string.auth_oauth_error;
964 break;
965 case OAUTH2_ERROR_ACCESS_DENIED:
966 mServerStatusText = R.string.auth_oauth_error_access_denied;
967 break;
968 case UNHANDLED_HTTP_CODE:
969 case UNKNOWN_ERROR:
970 mServerStatusText = R.string.auth_unknown_error_title;
971 break;
972 default:
973 mServerStatusText = 0;
974 mServerStatusIcon = 0;
975 }
976 }
977
978
979 /**
980 * Chooses the right icon and text to show to the user for the received operation result.
981 *
982 * @param result Result of a remote operation performed in this activity
983 */
984 private void updateAuthStatusIconAndText(RemoteOperationResult result) {
985 mAuthStatusIcon = R.drawable.common_error; // the most common case in the switch below
986
987 switch (result.getCode()) {
988 case OK_SSL:
989 mAuthStatusIcon = android.R.drawable.ic_secure;
990 mAuthStatusText = R.string.auth_secure_connection;
991 break;
992
993 case OK_NO_SSL:
994 case OK:
995 if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) {
996 mAuthStatusText = R.string.auth_connection_established;
997 mAuthStatusIcon = R.drawable.ic_ok;
998 } else {
999 mAuthStatusText = R.string.auth_nossl_plain_ok_title;
1000 mAuthStatusIcon = android.R.drawable.ic_partial_secure;
1001 }
1002 break;
1003
1004 case NO_NETWORK_CONNECTION:
1005 mAuthStatusIcon = R.drawable.no_network;
1006 mAuthStatusText = R.string.auth_no_net_conn_title;
1007 break;
1008
1009 case SSL_RECOVERABLE_PEER_UNVERIFIED:
1010 mAuthStatusText = R.string.auth_ssl_unverified_server_title;
1011 break;
1012 case BAD_OC_VERSION:
1013 mAuthStatusText = R.string.auth_bad_oc_version_title;
1014 break;
1015 case WRONG_CONNECTION:
1016 mAuthStatusText = R.string.auth_wrong_connection_title;
1017 break;
1018 case TIMEOUT:
1019 mAuthStatusText = R.string.auth_timeout_title;
1020 break;
1021 case INCORRECT_ADDRESS:
1022 mAuthStatusText = R.string.auth_incorrect_address_title;
1023 break;
1024 case SSL_ERROR:
1025 mAuthStatusText = R.string.auth_ssl_general_error_title;
1026 break;
1027 case UNAUTHORIZED:
1028 mAuthStatusText = R.string.auth_unauthorized;
1029 break;
1030 case HOST_NOT_AVAILABLE:
1031 mAuthStatusText = R.string.auth_unknown_host_title;
1032 break;
1033 case INSTANCE_NOT_CONFIGURED:
1034 mAuthStatusText = R.string.auth_not_configured_title;
1035 break;
1036 case FILE_NOT_FOUND:
1037 mAuthStatusText = R.string.auth_incorrect_path_title;
1038 break;
1039 case OAUTH2_ERROR:
1040 mAuthStatusText = R.string.auth_oauth_error;
1041 break;
1042 case OAUTH2_ERROR_ACCESS_DENIED:
1043 mAuthStatusText = R.string.auth_oauth_error_access_denied;
1044 break;
1045 case ACCOUNT_NOT_NEW:
1046 mAuthStatusText = R.string.auth_account_not_new;
1047 break;
1048 case ACCOUNT_NOT_THE_SAME:
1049 mAuthStatusText = R.string.auth_account_not_the_same;
1050 break;
1051 case UNHANDLED_HTTP_CODE:
1052 case UNKNOWN_ERROR:
1053 mAuthStatusText = R.string.auth_unknown_error_title;
1054 break;
1055 default:
1056 mAuthStatusText = 0;
1057 mAuthStatusIcon = 0;
1058 }
1059 }
1060
1061
1062 /**
1063 * Processes the result of the request for and access token send
1064 * to an OAuth authorization server.
1065 *
1066 * @param operation Operation performed requesting the access token.
1067 * @param result Result of the operation.
1068 */
1069 private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) {
1070 try {
1071 dismissDialog(DIALOG_OAUTH2_LOGIN_PROGRESS);
1072 } catch (IllegalArgumentException e) {
1073 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
1074 }
1075
1076 String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType);
1077 if (result.isSuccess() && webdav_path != null) {
1078 /// be gentle with the user
1079 showDialog(DIALOG_LOGIN_PROGRESS);
1080
1081 /// time to test the retrieved access token on the ownCloud server
1082 mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN);
1083 Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken);
1084 mAuthCheckOperation = new ExistenceCheckOperation("", this, false);
1085 WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true);
1086 client.setBearerCredentials(mAuthToken);
1087 mAuthCheckOperation.execute(client, this, mHandler);
1088
1089 } else {
1090 updateAuthStatusIconAndText(result);
1091 showAuthStatus();
1092 Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
1093 }
1094 }
1095
1096
1097 /**
1098 * Processes the result of the access check performed to try the user credentials.
1099 *
1100 * Creates a new account through the AccountManager.
1101 *
1102 * @param operation Access check performed.
1103 * @param result Result of the operation.
1104 */
1105 private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) {
1106 try {
1107 dismissDialog(DIALOG_LOGIN_PROGRESS);
1108 } catch (IllegalArgumentException e) {
1109 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
1110 }
1111
1112 if (result.isSuccess()) {
1113 Log_OC.d(TAG, "Successful access - time to save the account");
1114
1115 boolean success = false;
1116 if (mAction == ACTION_CREATE) {
1117 success = createAccount();
1118
1119 } else {
1120 success = updateToken();
1121 }
1122
1123 if (success) {
1124 finish();
1125 }
1126
1127 } else if (result.isServerFail() || result.isException()) {
1128 /// if server fail or exception in authorization, the UI is updated as when a server check failed
1129 mServerIsChecked = true;
1130 mServerIsValid = false;
1131 mIsSslConn = false;
1132 mOcServerChkOperation = null;
1133 mDiscoveredVersion = null;
1134 mHostBaseUrl = normalizeUrl(mHostUrlInput.getText().toString());
1135
1136 // update status icon and text
1137 updateServerStatusIconAndText(result);
1138 showServerStatus();
1139 mAuthStatusIcon = 0;
1140 mAuthStatusText = 0;
1141 showAuthStatus();
1142
1143 // update input controls state
1144 showRefreshButton();
1145 mOkButton.setEnabled(false);
1146
1147 // very special case (TODO: move to a common place for all the remote operations) (dangerous here?)
1148 if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
1149 mLastSslUntrustedServerResult = result;
1150 showDialog(DIALOG_SSL_VALIDATOR);
1151 }
1152
1153 } else { // authorization fail due to client side - probably wrong credentials
1154 updateAuthStatusIconAndText(result);
1155 showAuthStatus();
1156 Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
1157 }
1158
1159 }
1160
1161
1162 /**
1163 * Sets the proper response to get that the Account Authenticator that started this activity saves
1164 * a new authorization token for mAccount.
1165 */
1166 private boolean updateToken() {
1167 Bundle response = new Bundle();
1168 response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
1169 response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
1170
1171 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) {
1172 response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
1173 // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention
1174 mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
1175
1176 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) {
1177 String username = getUserNameForSamlSso();
1178 if (!mUsernameInput.getText().toString().equals(username)) {
1179 // fail - not a new account, but an existing one; disallow
1180 RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME);
1181 updateAuthStatusIconAndText(result);
1182 showAuthStatus();
1183 Log_OC.d(TAG, result.getLogMessage());
1184
1185 return false;
1186 }
1187
1188 response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
1189 // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention
1190 mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
1191
1192 } else {
1193 response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
1194 mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
1195 }
1196 setAccountAuthenticatorResult(response);
1197
1198 return true;
1199 }
1200
1201
1202 /**
1203 * Creates a new account through the Account Authenticator that started this activity.
1204 *
1205 * This makes the account permanent.
1206 *
1207 * TODO Decide how to name the OAuth accounts
1208 */
1209 private boolean createAccount() {
1210 /// create and save new ownCloud account
1211 boolean isOAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType);
1212 boolean isSaml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType);
1213
1214 Uri uri = Uri.parse(mHostBaseUrl);
1215 String username = mUsernameInput.getText().toString().trim();
1216 if (isSaml) {
1217 username = getUserNameForSamlSso();
1218
1219 } else if (isOAuth) {
1220 username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
1221 }
1222 String accountName = username + "@" + uri.getHost();
1223 if (uri.getPort() >= 0) {
1224 accountName += ":" + uri.getPort();
1225 }
1226 mAccount = new Account(accountName, MainApp.getAccountType());
1227 if (AccountUtils.exists(mAccount, getApplicationContext())) {
1228 // fail - not a new account, but an existing one; disallow
1229 RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW);
1230 updateAuthStatusIconAndText(result);
1231 showAuthStatus();
1232 Log_OC.d(TAG, result.getLogMessage());
1233 return false;
1234
1235 } else {
1236
1237 if (isOAuth || isSaml) {
1238 mAccountMgr.addAccountExplicitly(mAccount, "", null); // with external authorizations, the password is never input in the app
1239 } else {
1240 mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null);
1241 }
1242
1243 /// add the new account as default in preferences, if there is none already
1244 Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);
1245 if (defaultAccount == null) {
1246 SharedPreferences.Editor editor = PreferenceManager
1247 .getDefaultSharedPreferences(this).edit();
1248 editor.putString("select_oc_account", accountName);
1249 editor.commit();
1250 }
1251
1252 /// prepare result to return to the Authenticator
1253 // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done
1254 final Intent intent = new Intent();
1255 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType());
1256 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
1257 /*if (!isOAuth)
1258 intent.putExtra(AccountManager.KEY_AUTHTOKEN, MainApp.getAccountType()); */
1259 intent.putExtra(AccountManager.KEY_USERDATA, username);
1260 if (isOAuth || isSaml) {
1261 mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
1262 }
1263 /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA
1264 mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_VERSION, mDiscoveredVersion.toString());
1265 mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_BASE_URL, mHostBaseUrl);
1266 if (isSaml) {
1267 mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE");
1268 } else if (isOAuth) {
1269 mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_OAUTH2, "TRUE");
1270 }
1271
1272 setAccountAuthenticatorResult(intent.getExtras());
1273 setResult(RESULT_OK, intent);
1274
1275 /// immediately request for the synchronization of the new account
1276 Bundle bundle = new Bundle();
1277 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
1278 ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle);
1279 syncAccount();
1280 // Bundle bundle = new Bundle();
1281 // bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
1282 // ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle);
1283 return true;
1284 }
1285 }
1286
1287
1288 private String getUserNameForSamlSso() {
1289 if (mAuthToken != null) {
1290 String [] cookies = mAuthToken.split(";");
1291 for (int i=0; i<cookies.length; i++) {
1292 if (cookies[i].startsWith(KEY_OC_USERNAME_EQUALS )) {
1293 String value = Uri.decode(cookies[i].substring(KEY_OC_USERNAME_EQUALS.length()));
1294 return value;
1295 }
1296 }
1297 }
1298 return "";
1299 }
1300
1301
1302 /**
1303 * {@inheritDoc}
1304 *
1305 * Necessary to update the contents of the SSL Dialog
1306 *
1307 * TODO move to some common place for all possible untrusted SSL failures
1308 */
1309 @Override
1310 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
1311 switch (id) {
1312 case DIALOG_LOGIN_PROGRESS:
1313 case DIALOG_CERT_NOT_SAVED:
1314 case DIALOG_OAUTH2_LOGIN_PROGRESS:
1315 break;
1316 case DIALOG_SSL_VALIDATOR: {
1317 ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);
1318 break;
1319 }
1320 default:
1321 Log_OC.e(TAG, "Incorrect dialog called with id = " + id);
1322 }
1323 }
1324
1325
1326 /**
1327 * {@inheritDoc}
1328 */
1329 @Override
1330 protected Dialog onCreateDialog(int id) {
1331 Dialog dialog = null;
1332 switch (id) {
1333 case DIALOG_LOGIN_PROGRESS: {
1334 /// simple progress dialog
1335 ProgressDialog working_dialog = new ProgressDialog(this);
1336 working_dialog.setMessage(getResources().getString(R.string.auth_trying_to_login));
1337 working_dialog.setIndeterminate(true);
1338 working_dialog.setCancelable(true);
1339 working_dialog
1340 .setOnCancelListener(new DialogInterface.OnCancelListener() {
1341 @Override
1342 public void onCancel(DialogInterface dialog) {
1343 /// TODO study if this is enough
1344 Log_OC.i(TAG, "Login canceled");
1345 if (mOperationThread != null) {
1346 mOperationThread.interrupt();
1347 finish();
1348 }
1349 }
1350 });
1351 dialog = working_dialog;
1352 break;
1353 }
1354 case DIALOG_OAUTH2_LOGIN_PROGRESS: {
1355 ProgressDialog working_dialog = new ProgressDialog(this);
1356 working_dialog.setMessage(String.format("Getting authorization"));
1357 working_dialog.setIndeterminate(true);
1358 working_dialog.setCancelable(true);
1359 working_dialog
1360 .setOnCancelListener(new DialogInterface.OnCancelListener() {
1361 @Override
1362 public void onCancel(DialogInterface dialog) {
1363 Log_OC.i(TAG, "Login canceled");
1364 finish();
1365 }
1366 });
1367 dialog = working_dialog;
1368 break;
1369 }
1370 case DIALOG_SSL_VALIDATOR: {
1371 /// TODO start to use new dialog interface, at least for this (it is a FragmentDialog already)
1372 dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);
1373 break;
1374 }
1375 case DIALOG_CERT_NOT_SAVED: {
1376 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1377 builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));
1378 builder.setCancelable(false);
1379 builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
1380 @Override
1381 public void onClick(DialogInterface dialog, int which) {
1382 dialog.dismiss();
1383 };
1384 });
1385 dialog = builder.create();
1386 break;
1387 }
1388 default:
1389 Log_OC.e(TAG, "Incorrect dialog called with id = " + id);
1390 }
1391 return dialog;
1392 }
1393
1394
1395 /**
1396 * Starts and activity to open the 'new account' page in the ownCloud web site
1397 *
1398 * @param view 'Account register' button
1399 */
1400 public void onRegisterClick(View view) {
1401 Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));
1402 setResult(RESULT_CANCELED);
1403 startActivity(register);
1404 }
1405
1406
1407 /**
1408 * Updates the content and visibility state of the icon and text associated
1409 * to the last check on the ownCloud server.
1410 */
1411 private void showServerStatus() {
1412 TextView tv = (TextView) findViewById(R.id.server_status_text);
1413
1414 if (mServerStatusIcon == 0 && mServerStatusText == 0) {
1415 tv.setVisibility(View.INVISIBLE);
1416
1417 } else {
1418 tv.setText(mServerStatusText);
1419 tv.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon, 0, 0, 0);
1420 tv.setVisibility(View.VISIBLE);
1421 }
1422
1423 }
1424
1425
1426 /**
1427 * Updates the content and visibility state of the icon and text associated
1428 * to the interactions with the OAuth authorization server.
1429 */
1430 private void showAuthStatus() {
1431 if (mAuthStatusIcon == 0 && mAuthStatusText == 0) {
1432 mAuthStatusLayout.setVisibility(View.INVISIBLE);
1433
1434 } else {
1435 mAuthStatusLayout.setText(mAuthStatusText);
1436 mAuthStatusLayout.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
1437 mAuthStatusLayout.setVisibility(View.VISIBLE);
1438 }
1439 }
1440
1441
1442 private void showRefreshButton() {
1443 mRefreshButton.setVisibility(View.VISIBLE);
1444 }
1445
1446 private void hideRefreshButton() {
1447 mRefreshButton.setVisibility(View.GONE);
1448 }
1449
1450 /**
1451 * Called when the refresh button in the input field for ownCloud host is clicked.
1452 *
1453 * Performs a new check on the URL in the input field.
1454 *
1455 * @param view Refresh 'button'
1456 */
1457 public void onRefreshClick(View view) {
1458 checkOcServer();
1459 }
1460
1461
1462 /**
1463 * Called when the eye icon in the password field is clicked.
1464 *
1465 * Toggles the visibility of the password in the field.
1466 */
1467 public void onViewPasswordClick() {
1468 int selectionStart = mPasswordInput.getSelectionStart();
1469 int selectionEnd = mPasswordInput.getSelectionEnd();
1470 if (isPasswordVisible()) {
1471 hidePassword();
1472 } else {
1473 showPassword();
1474 }
1475 mPasswordInput.setSelection(selectionStart, selectionEnd);
1476 }
1477
1478
1479 /**
1480 * Called when the checkbox for OAuth authorization is clicked.
1481 *
1482 * Hides or shows the input fields for user & password.
1483 *
1484 * @param view 'View password' 'button'
1485 */
1486 public void onCheckClick(View view) {
1487 CheckBox oAuth2Check = (CheckBox)view;
1488 if (oAuth2Check.isChecked()) {
1489 mAuthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType());
1490 } else {
1491 mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType());
1492 }
1493 adaptViewAccordingToAuthenticationMethod();
1494 }
1495
1496
1497 /**
1498 * Changes the visibility of input elements depending on
1499 * the current authorization method.
1500 */
1501 private void adaptViewAccordingToAuthenticationMethod () {
1502 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) {
1503 // OAuth 2 authorization
1504 mOAuthAuthEndpointText.setVisibility(View.VISIBLE);
1505 mOAuthTokenEndpointText.setVisibility(View.VISIBLE);
1506 mUsernameInput.setVisibility(View.GONE);
1507 mPasswordInput.setVisibility(View.GONE);
1508
1509 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) {
1510 // SAML-based web Single Sign On
1511 mOAuthAuthEndpointText.setVisibility(View.GONE);
1512 mOAuthTokenEndpointText.setVisibility(View.GONE);
1513 mUsernameInput.setVisibility(View.GONE);
1514 mPasswordInput.setVisibility(View.GONE);
1515 } else {
1516 // basic HTTP authorization
1517 mOAuthAuthEndpointText.setVisibility(View.GONE);
1518 mOAuthTokenEndpointText.setVisibility(View.GONE);
1519 mUsernameInput.setVisibility(View.VISIBLE);
1520 mPasswordInput.setVisibility(View.VISIBLE);
1521 }
1522 }
1523
1524 /**
1525 * Called from SslValidatorDialog when a new server certificate was correctly saved.
1526 */
1527 public void onSavedCertificate() {
1528 checkOcServer();
1529 }
1530
1531 /**
1532 * Called from SslValidatorDialog when a new server certificate could not be saved
1533 * when the user requested it.
1534 */
1535 @Override
1536 public void onFailedSavingCertificate() {
1537 showDialog(DIALOG_CERT_NOT_SAVED);
1538 }
1539
1540
1541 /**
1542 * Called when the 'action' button in an IME is pressed ('enter' in software keyboard).
1543 *
1544 * Used to trigger the authentication check when the user presses 'enter' after writing the password,
1545 * or to throw the server test when the only field on screen is the URL input field.
1546 */
1547 @Override
1548 public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) {
1549 if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null && inputField.equals(mPasswordInput)) {
1550 if (mOkButton.isEnabled()) {
1551 mOkButton.performClick();
1552 }
1553
1554 } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null && inputField.equals(mHostUrlInput)) {
1555 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) {
1556 checkOcServer();
1557 }
1558 }
1559 return false; // always return false to grant that the software keyboard is hidden anyway
1560 }
1561
1562
1563 private abstract static class RightDrawableOnTouchListener implements OnTouchListener {
1564
1565 private int fuzz = 75;
1566
1567 /**
1568 * {@inheritDoc}
1569 */
1570 @Override
1571 public boolean onTouch(View view, MotionEvent event) {
1572 Drawable rightDrawable = null;
1573 if (view instanceof TextView) {
1574 Drawable[] drawables = ((TextView)view).getCompoundDrawables();
1575 if (drawables.length > 2) {
1576 rightDrawable = drawables[2];
1577 }
1578 }
1579 if (rightDrawable != null) {
1580 final int x = (int) event.getX();
1581 final int y = (int) event.getY();
1582 final Rect bounds = rightDrawable.getBounds();
1583 if (x >= (view.getRight() - bounds.width() - fuzz) && x <= (view.getRight() - view.getPaddingRight() + fuzz)
1584 && y >= (view.getPaddingTop() - fuzz) && y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
1585
1586 return onDrawableTouch(event);
1587 }
1588 }
1589 return false;
1590 }
1591
1592 public abstract boolean onDrawableTouch(final MotionEvent event);
1593 }
1594
1595
1596 public void onSamlDialogSuccess(String sessionCookie){
1597 mAuthToken = sessionCookie;
1598
1599 if (sessionCookie != null && sessionCookie.length() > 0) {
1600 mAuthToken = sessionCookie;
1601 boolean success = false;
1602 if (mAction == ACTION_CREATE) {
1603 success = createAccount();
1604
1605 } else {
1606 success = updateToken();
1607 }
1608 if (success) {
1609 finish();
1610 }
1611 }
1612
1613
1614 }
1615
1616
1617 @Override
1618 public void onSsoFinished(String sessionCookies) {
1619 //Toast.makeText(this, "got cookies: " + sessionCookie, Toast.LENGTH_LONG).show();
1620
1621 if (sessionCookies != null && sessionCookies.length() > 0) {
1622 Log_OC.d(TAG, "Successful SSO - time to save the account");
1623 onSamlDialogSuccess(sessionCookies);
1624 Fragment fd = getSupportFragmentManager().findFragmentByTag(TAG_SAML_DIALOG);
1625 if (fd != null && fd instanceof SherlockDialogFragment) {
1626 Dialog d = ((SherlockDialogFragment)fd).getDialog();
1627 if (d != null && d.isShowing()) {
1628 d.dismiss();
1629 }
1630 }
1631
1632 } else {
1633 // TODO - show fail
1634 Log_OC.d(TAG, "SSO failed");
1635 }
1636
1637 }
1638
1639 /** Show auth_message
1640 *
1641 * @param message
1642 */
1643 private void showAuthMessage(String message) {
1644 mAuthMessage.setVisibility(View.VISIBLE);
1645 mAuthMessage.setText(message);
1646 }
1647
1648 private void hideAuthMessage() {
1649 mAuthMessage.setVisibility(View.GONE);
1650 }
1651
1652 private void syncAccount(){
1653 /// immediately request for the synchronization of the new account
1654 Bundle bundle = new Bundle();
1655 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
1656 ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle);
1657 }
1658
1659 @Override
1660 public boolean onTouchEvent(MotionEvent event) {
1661 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) &&
1662 mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) {
1663 checkOcServer();
1664 }
1665 return super.onTouchEvent(event);
1666 }
1667 }