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