Merge branch 'material_toolbar' of https://github.com/owncloud/android into material_...
[pub/Android/ownCloud.git] / src / com / owncloud / android / authentication / AuthenticatorActivity.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author Bartek Przybylski
5 * @author David A. Velasco
6 * @author masensio
7 * Copyright (C) 2012 Bartek Przybylski
8 * Copyright (C) 2015 ownCloud Inc.
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23
24 package com.owncloud.android.authentication;
25
26 import android.accounts.Account;
27 import android.accounts.AccountManager;
28 import android.app.Dialog;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.content.SharedPreferences;
34 import android.graphics.Rect;
35 import android.graphics.drawable.Drawable;
36 import android.net.Uri;
37 import android.net.http.SslError;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.preference.PreferenceManager;
42 import android.support.v4.app.DialogFragment;
43 import android.support.v4.app.Fragment;
44 import android.support.v4.app.FragmentManager;
45 import android.support.v4.app.FragmentTransaction;
46 import android.text.Editable;
47 import android.text.InputType;
48 import android.text.TextWatcher;
49 import android.view.KeyEvent;
50 import android.view.MotionEvent;
51 import android.view.View;
52 import android.view.View.OnFocusChangeListener;
53 import android.view.View.OnTouchListener;
54 import android.view.inputmethod.EditorInfo;
55 import android.webkit.HttpAuthHandler;
56 import android.webkit.SslErrorHandler;
57 import android.webkit.WebView;
58 import android.widget.Button;
59 import android.widget.CheckBox;
60 import android.widget.EditText;
61 import android.widget.TextView;
62 import android.widget.TextView.OnEditorActionListener;
63 import android.widget.Toast;
64
65 import com.owncloud.android.MainApp;
66 import com.owncloud.android.R;
67 import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener;
68 import com.owncloud.android.lib.common.OwnCloudCredentials;
69 import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
70 import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
71 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
72 import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
73 import com.owncloud.android.lib.common.network.CertificateCombinedException;
74 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
75 import com.owncloud.android.lib.common.operations.RemoteOperation;
76 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
77 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
78 import com.owncloud.android.lib.common.utils.Log_OC;
79 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
80 import com.owncloud.android.lib.resources.users.GetRemoteUserNameOperation;
81 import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod;
82 import com.owncloud.android.operations.GetServerInfoOperation;
83 import com.owncloud.android.operations.OAuth2GetAccessToken;
84 import com.owncloud.android.services.OperationsService;
85 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
86 import com.owncloud.android.ui.dialog.CredentialsDialogFragment;
87 import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
88 import com.owncloud.android.ui.dialog.SamlWebViewDialog;
89 import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
90 import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
91 import com.owncloud.android.utils.DisplayUtils;
92
93 import java.security.cert.X509Certificate;
94 import java.util.Map;
95
96 /**
97 * This Activity is used to add an ownCloud account to the App
98 */
99 public class AuthenticatorActivity extends AccountAuthenticatorActivity
100 implements OnRemoteOperationListener, OnFocusChangeListener, OnEditorActionListener,
101 SsoWebViewClientListener, OnSslUntrustedCertListener,
102 AuthenticatorAsyncTask.OnAuthenticatorTaskListener {
103
104 private static final String TAG = AuthenticatorActivity.class.getSimpleName();
105
106 public static final String EXTRA_ACTION = "ACTION";
107 public static final String EXTRA_ACCOUNT = "ACCOUNT";
108
109 private static final String KEY_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE";
110
111 private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT";
112 private static final String KEY_OC_VERSION = "OC_VERSION";
113 private static final String KEY_SERVER_VALID = "SERVER_VALID";
114 private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED";
115 private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT";
116 private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON";
117 private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN";
118 private static final String KEY_PASSWORD_EXPOSED = "PASSWORD_VISIBLE";
119 private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT";
120 private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON";
121 private static final String KEY_SERVER_AUTH_METHOD = "SERVER_AUTH_METHOD";
122 private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID";
123 private static final String KEY_AUTH_TOKEN = "AUTH_TOKEN";
124
125 private static final String AUTH_ON = "on";
126 private static final String AUTH_OPTIONAL = "optional";
127
128 public static final byte ACTION_CREATE = 0;
129 public static final byte ACTION_UPDATE_TOKEN = 1; // requested by the user
130 public static final byte ACTION_UPDATE_EXPIRED_TOKEN = 2; // detected by the app
131
132 private static final String UNTRUSTED_CERT_DIALOG_TAG = "UNTRUSTED_CERT_DIALOG";
133 private static final String SAML_DIALOG_TAG = "SAML_DIALOG";
134 private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG";
135 private static final String CREDENTIALS_DIALOG_TAG = "CREDENTIALS_DIALOG";
136 private static final String KEY_AUTH_IS_FIRST_ATTEMPT_TAG = "KEY_AUTH_IS_FIRST_ATTEMPT";
137
138 private static final String KEY_USERNAME = "USERNAME";
139 private static final String KEY_PASSWORD = "PASSWORD";
140 private static final String KEY_ASYNC_TASK_IN_PROGRESS = "AUTH_IN_PROGRESS";
141
142 /// parameters from EXTRAs in starter Intent
143 private byte mAction;
144 private Account mAccount;
145 private String mAuthTokenType;
146
147
148 /// activity-level references / state
149 private final Handler mHandler = new Handler();
150 private ServiceConnection mOperationsServiceConnection = null;
151 private OperationsServiceBinder mOperationsServiceBinder = null;
152 private AccountManager mAccountMgr;
153 private Uri mNewCapturedUriFromOAuth2Redirection;
154
155
156 /// Server PRE-Fragment elements
157 private EditText mHostUrlInput;
158 private View mRefreshButton;
159 private TextView mServerStatusView;
160
161 private TextWatcher mHostUrlInputWatcher;
162 private int mServerStatusText = 0, mServerStatusIcon = 0;
163
164 private boolean mServerIsChecked = false;
165 private boolean mServerIsValid = false;
166 private boolean mPendingAutoCheck = false;
167
168 private GetServerInfoOperation.ServerInfo mServerInfo =
169 new GetServerInfoOperation.ServerInfo();
170
171
172 /// Authentication PRE-Fragment elements
173 private CheckBox mOAuth2Check;
174 private TextView mOAuthAuthEndpointText;
175 private TextView mOAuthTokenEndpointText;
176 private EditText mUsernameInput;
177 private EditText mPasswordInput;
178 private View mOkButton;
179 private View mCenteredRefreshButton;
180 private TextView mAuthStatusView;
181
182 private int mAuthStatusText = 0, mAuthStatusIcon = 0;
183
184 private String mAuthToken = "";
185 private AuthenticatorAsyncTask mAsyncTask;
186
187 private boolean mIsFirstAuthAttempt;
188
189 /// Identifier of operation in progress which result shouldn't be lost
190 private long mWaitingForOpId = Long.MAX_VALUE;
191
192 private final String BASIC_TOKEN_TYPE = AccountTypeUtils.getAuthTokenTypePass(
193 MainApp.getAccountType());
194 private final String OAUTH_TOKEN_TYPE = AccountTypeUtils.getAuthTokenTypeAccessToken(
195 MainApp.getAccountType());
196 private final String SAML_TOKEN_TYPE =
197 AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType());
198
199
200 /**
201 * {@inheritDoc}
202 *
203 * IMPORTANT ENTRY POINT 1: activity is shown to the user
204 */
205 @Override
206 protected void onCreate(Bundle savedInstanceState) {
207 //Log_OC.wtf(TAG, "onCreate init");
208 super.onCreate(savedInstanceState);
209
210 // Workaround, for fixing a problem with Android Library Suppor v7 19
211 //getWindow().requestFeature(Window.FEATURE_NO_TITLE);
212 if (getSupportActionBar() != null) {
213 getSupportActionBar().hide();
214
215 getSupportActionBar().setDisplayHomeAsUpEnabled(false);
216 getSupportActionBar().setDisplayShowHomeEnabled(false);
217 getSupportActionBar().setDisplayShowTitleEnabled(false);
218 }
219
220 mIsFirstAuthAttempt = true;
221
222 // bind to Operations Service
223 mOperationsServiceConnection = new OperationsServiceConnection();
224 if (!bindService(new Intent(this, OperationsService.class),
225 mOperationsServiceConnection,
226 Context.BIND_AUTO_CREATE)) {
227 Toast.makeText(this,
228 R.string.error_cant_bind_to_operations_service,
229 Toast.LENGTH_LONG)
230 .show();
231 finish();
232 }
233
234 /// init activity state
235 mAccountMgr = AccountManager.get(this);
236 mNewCapturedUriFromOAuth2Redirection = null;
237
238 /// get input values
239 mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE);
240 mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
241 if (savedInstanceState == null) {
242 initAuthTokenType();
243 } else {
244 mAuthTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE);
245 mWaitingForOpId = savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID);
246 mIsFirstAuthAttempt = savedInstanceState.getBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG);
247 }
248
249 /// load user interface
250 setContentView(R.layout.account_setup);
251
252 /// initialize general UI elements
253 initOverallUi();
254
255 mOkButton = findViewById(R.id.buttonOK);
256 mOkButton.setOnClickListener(new View.OnClickListener() {
257
258 @Override
259 public void onClick(View v) {
260 onOkClick();
261 }
262 });
263
264 mCenteredRefreshButton = findViewById(R.id.centeredRefreshButton);
265 mCenteredRefreshButton.setOnClickListener(new View.OnClickListener() {
266
267 @Override
268 public void onClick(View v) {
269 checkOcServer();
270 }
271 });
272
273
274 /// initialize block to be moved to single Fragment to check server and get info about it
275 initServerPreFragment(savedInstanceState);
276
277 /// initialize block to be moved to single Fragment to retrieve and validate credentials
278 initAuthorizationPreFragment(savedInstanceState);
279
280 //Log_OC.wtf(TAG, "onCreate end");
281 }
282
283 private void initAuthTokenType() {
284 mAuthTokenType =
285 getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);
286 if (mAuthTokenType == null) {
287 if (mAccount != null) {
288 boolean oAuthRequired =
289 (mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2) != null);
290 boolean samlWebSsoRequired = (
291 mAccountMgr.getUserData(
292 mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO
293 ) != null
294 );
295 mAuthTokenType = chooseAuthTokenType(oAuthRequired, samlWebSsoRequired);
296
297 } else {
298 boolean oAuthSupported = AUTH_ON.equals(getString(R.string.auth_method_oauth2));
299 boolean samlWebSsoSupported =
300 AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));
301 mAuthTokenType = chooseAuthTokenType(oAuthSupported, samlWebSsoSupported);
302 }
303 }
304 }
305
306 private String chooseAuthTokenType(boolean oauth, boolean saml) {
307 if (saml) {
308 return SAML_TOKEN_TYPE;
309 } else if (oauth) {
310 return OAUTH_TOKEN_TYPE;
311 } else {
312 return BASIC_TOKEN_TYPE;
313 }
314 }
315
316
317 /**
318 * Configures elements in the user interface under direct control of the Activity.
319 */
320 private void initOverallUi() {
321
322 /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
323 boolean isWelcomeLinkVisible = getResources().getBoolean(R.bool.show_welcome_link);
324
325 String instructionsMessageText = null;
326 if (mAction == ACTION_UPDATE_EXPIRED_TOKEN) {
327 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType())
328 .equals(mAuthTokenType)) {
329 instructionsMessageText = getString(R.string.auth_expired_oauth_token_toast);
330
331 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType())
332 .equals(mAuthTokenType)) {
333 instructionsMessageText = getString(R.string.auth_expired_saml_sso_token_toast);
334
335 } else {
336 instructionsMessageText = getString(R.string.auth_expired_basic_auth_toast);
337 }
338 }
339
340 /// step 2 - set properties of UI elements (text, visibility, enabled...)
341 Button welcomeLink = (Button) findViewById(R.id.welcome_link);
342 welcomeLink.setVisibility(isWelcomeLinkVisible ? View.VISIBLE : View.GONE);
343 welcomeLink.setText(
344 String.format(getString(R.string.auth_register), getString(R.string.app_name)));
345
346 TextView instructionsView = (TextView) findViewById(R.id.instructions_message);
347 if (instructionsMessageText != null) {
348 instructionsView.setVisibility(View.VISIBLE);
349 instructionsView.setText(instructionsMessageText);
350 } else {
351 instructionsView.setVisibility(View.GONE);
352 }
353 }
354
355
356 /**
357 *
358 * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
359 */
360 private void initServerPreFragment(Bundle savedInstanceState) {
361
362 /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
363 boolean isUrlInputAllowed = getResources().getBoolean(R.bool.show_server_url_input);
364 if (savedInstanceState == null) {
365 if (mAccount != null) {
366 mServerInfo.mBaseUrl = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_BASE_URL);
367 // TODO do next in a setter for mBaseUrl
368 mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith("https://");
369 mServerInfo.mVersion = AccountUtils.getServerVersion(mAccount);
370 } else {
371 mServerInfo.mBaseUrl = getString(R.string.server_url).trim();
372 mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith("https://");
373 }
374 } else {
375 mServerStatusText = savedInstanceState.getInt(KEY_SERVER_STATUS_TEXT);
376 mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON);
377
378 mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID);
379 mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED);
380
381 // TODO parcelable
382 mServerInfo.mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN);
383 mServerInfo.mBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT);
384 String ocVersion = savedInstanceState.getString(KEY_OC_VERSION);
385 if (ocVersion != null) {
386 mServerInfo.mVersion = new OwnCloudVersion(ocVersion);
387 }
388 mServerInfo.mAuthMethod = AuthenticationMethod.valueOf(
389 savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));
390
391 }
392
393 /// step 2 - set properties of UI elements (text, visibility, enabled...)
394 mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput);
395 // Convert IDN to Unicode
396 mHostUrlInput.setText(DisplayUtils.convertIdn(mServerInfo.mBaseUrl, false));
397 if (mAction != ACTION_CREATE) {
398 /// lock things that should not change
399 mHostUrlInput.setEnabled(false);
400 mHostUrlInput.setFocusable(false);
401 }
402 if (isUrlInputAllowed) {
403 mRefreshButton = findViewById(R.id.embeddedRefreshButton);
404 } else {
405 findViewById(R.id.hostUrlFrame).setVisibility(View.GONE);
406 mRefreshButton = findViewById(R.id.centeredRefreshButton);
407 }
408 showRefreshButton(mServerIsChecked && !mServerIsValid &&
409 mWaitingForOpId > Integer.MAX_VALUE);
410 mServerStatusView = (TextView) findViewById(R.id.server_status_text);
411 showServerStatus();
412
413 /// step 3 - bind some listeners and options
414 mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
415 mHostUrlInput.setOnEditorActionListener(this);
416
417 /// step 4 - create listeners that will be bound at onResume
418 mHostUrlInputWatcher = new TextWatcher() {
419
420 @Override
421 public void afterTextChanged(Editable s) {
422 if (mOkButton.isEnabled() &&
423 !mServerInfo.mBaseUrl.equals(
424 normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) {
425 mOkButton.setEnabled(false);
426 }
427 }
428
429 @Override
430 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
431 }
432
433 @Override
434 public void onTextChanged(CharSequence s, int start, int before, int count) {
435 if (mAuthStatusIcon != 0) {
436 Log_OC.d(TAG, "onTextChanged: hiding authentication status");
437 mAuthStatusIcon = 0;
438 mAuthStatusText = 0;
439 showAuthStatus();
440 }
441 }
442 };
443
444
445 // TODO find out if this is really necessary, or if it can done in a different way
446 findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() {
447 @Override
448 public boolean onTouch(View view, MotionEvent event) {
449 if (event.getAction() == MotionEvent.ACTION_DOWN) {
450 if (
451 AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(
452 MainApp.getAccountType()
453 ).equals(mAuthTokenType) &&
454 mHostUrlInput.hasFocus()
455 ) {
456 checkOcServer();
457 }
458 }
459 return false;
460 }
461 });
462
463
464 /// step 4 - mark automatic check to be started when OperationsService is ready
465 mPendingAutoCheck = (savedInstanceState == null &&
466 (mAction != ACTION_CREATE || !isUrlInputAllowed));
467 }
468
469
470 /**
471 *
472 * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
473 */
474 private void initAuthorizationPreFragment(Bundle savedInstanceState) {
475
476 /// step 0 - get UI elements in layout
477 mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
478 mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1);
479 mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2);
480 mUsernameInput = (EditText) findViewById(R.id.account_username);
481 mPasswordInput = (EditText) findViewById(R.id.account_password);
482 mAuthStatusView = (TextView) findViewById(R.id.auth_status_text);
483
484 /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
485 String presetUserName = null;
486 boolean isPasswordExposed = false;
487 if (savedInstanceState == null) {
488 if (mAccount != null) {
489 presetUserName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@'));
490 }
491
492 } else {
493 isPasswordExposed = savedInstanceState.getBoolean(KEY_PASSWORD_EXPOSED, false);
494 mAuthStatusText = savedInstanceState.getInt(KEY_AUTH_STATUS_TEXT);
495 mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON);
496 mAuthToken = savedInstanceState.getString(KEY_AUTH_TOKEN);
497 }
498
499 /// step 2 - set properties of UI elements (text, visibility, enabled...)
500 mOAuth2Check.setChecked(
501 AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType())
502 .equals(mAuthTokenType));
503 if (presetUserName != null) {
504 mUsernameInput.setText(presetUserName);
505 }
506 if (mAction != ACTION_CREATE) {
507 mUsernameInput.setEnabled(false);
508 mUsernameInput.setFocusable(false);
509 }
510 mPasswordInput.setText(""); // clean password to avoid social hacking
511 if (isPasswordExposed) {
512 showPassword();
513 }
514 updateAuthenticationPreFragmentVisibility();
515 showAuthStatus();
516 mOkButton.setEnabled(mServerIsValid);
517
518
519 /// step 3 - bind listeners
520 // bindings for password input field
521 mPasswordInput.setOnFocusChangeListener(this);
522 mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
523 mPasswordInput.setOnEditorActionListener(this);
524 mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() {
525 @Override
526 public boolean onDrawableTouch(final MotionEvent event) {
527 if (event.getAction() == MotionEvent.ACTION_UP) {
528 AuthenticatorActivity.this.onViewPasswordClick();
529 }
530 return true;
531 }
532 });
533
534 }
535
536
537 /**
538 * Changes the visibility of input elements depending on
539 * the current authorization method.
540 */
541 private void updateAuthenticationPreFragmentVisibility () {
542 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).
543 equals(mAuthTokenType)) {
544 // SAML-based web Single Sign On
545 mOAuth2Check.setVisibility(View.GONE);
546 mOAuthAuthEndpointText.setVisibility(View.GONE);
547 mOAuthTokenEndpointText.setVisibility(View.GONE);
548 mUsernameInput.setVisibility(View.GONE);
549 mPasswordInput.setVisibility(View.GONE);
550
551 } else {
552 if (mAction == ACTION_CREATE &&
553 AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) {
554 mOAuth2Check.setVisibility(View.VISIBLE);
555 } else {
556 mOAuth2Check.setVisibility(View.GONE);
557 }
558
559 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).
560 equals(mAuthTokenType)) {
561 // OAuth 2 authorization
562
563 mOAuthAuthEndpointText.setVisibility(View.VISIBLE);
564 mOAuthTokenEndpointText.setVisibility(View.VISIBLE);
565 mUsernameInput.setVisibility(View.GONE);
566 mPasswordInput.setVisibility(View.GONE);
567
568 } else {
569 // basic HTTP authorization
570 mOAuthAuthEndpointText.setVisibility(View.GONE);
571 mOAuthTokenEndpointText.setVisibility(View.GONE);
572 mUsernameInput.setVisibility(View.VISIBLE);
573 mPasswordInput.setVisibility(View.VISIBLE);
574 }
575 }
576 }
577
578
579
580 /**
581 * Saves relevant state before {@link #onPause()}
582 *
583 * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag,
584 * intended to defer the processing of the redirection caught in
585 * {@link #onNewIntent(Intent)} until {@link #onResume()}
586 *
587 * See {@link super#onSaveInstanceState(Bundle)}
588 */
589 @Override
590 protected void onSaveInstanceState(Bundle outState) {
591 //Log_OC.wtf(TAG, "onSaveInstanceState init" );
592 super.onSaveInstanceState(outState);
593
594 /// global state
595 outState.putString(KEY_AUTH_TOKEN_TYPE, mAuthTokenType);
596 outState.putLong(KEY_WAITING_FOR_OP_ID, mWaitingForOpId);
597
598 /// Server PRE-fragment state
599 outState.putInt(KEY_SERVER_STATUS_TEXT, mServerStatusText);
600 outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon);
601 outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked);
602 outState.putBoolean(KEY_SERVER_VALID, mServerIsValid);
603 outState.putBoolean(KEY_IS_SSL_CONN, mServerInfo.mIsSslConn);
604 outState.putString(KEY_HOST_URL_TEXT, mServerInfo.mBaseUrl);
605 if (mServerInfo.mVersion != null) {
606 outState.putString(KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
607 }
608 outState.putString(KEY_SERVER_AUTH_METHOD, mServerInfo.mAuthMethod.name());
609
610 /// Authentication PRE-fragment state
611 outState.putBoolean(KEY_PASSWORD_EXPOSED, isPasswordVisible());
612 outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon);
613 outState.putInt(KEY_AUTH_STATUS_TEXT, mAuthStatusText);
614 outState.putString(KEY_AUTH_TOKEN, mAuthToken);
615
616 /// authentication
617 outState.putBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG, mIsFirstAuthAttempt);
618
619 /// AsyncTask (User and password)
620 outState.putString(KEY_USERNAME, mUsernameInput.getText().toString());
621 outState.putString(KEY_PASSWORD, mPasswordInput.getText().toString());
622
623 if (mAsyncTask != null) {
624 mAsyncTask.cancel(true);
625 outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, true);
626 } else {
627 outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, false);
628 }
629 mAsyncTask = null;
630
631 //Log_OC.wtf(TAG, "onSaveInstanceState end" );
632 }
633
634 @Override
635 public void onRestoreInstanceState(Bundle savedInstanceState) {
636 super.onRestoreInstanceState(savedInstanceState);
637
638 // AsyncTask
639 boolean inProgress = savedInstanceState.getBoolean(KEY_ASYNC_TASK_IN_PROGRESS);
640 if (inProgress){
641 String username = savedInstanceState.getString(KEY_USERNAME);
642 String password = savedInstanceState.getString(KEY_PASSWORD);
643
644 OwnCloudCredentials credentials = null;
645 if (BASIC_TOKEN_TYPE.equals(mAuthTokenType)) {
646 credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
647
648 } else if (OAUTH_TOKEN_TYPE.equals(mAuthTokenType)) {
649 credentials = OwnCloudCredentialsFactory.newBearerCredentials(mAuthToken);
650
651 }
652 accessRootFolder(credentials);
653 }
654 }
655
656 /**
657 * The redirection triggered by the OAuth authentication server as response to the
658 * GET AUTHORIZATION request is caught here.
659 *
660 * To make this possible, this activity needs to be qualified with android:launchMode =
661 * "singleTask" in the AndroidManifest.xml file.
662 */
663 @Override
664 protected void onNewIntent (Intent intent) {
665 Log_OC.d(TAG, "onNewIntent()");
666 Uri data = intent.getData();
667 if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) {
668 mNewCapturedUriFromOAuth2Redirection = data;
669 }
670 }
671
672
673 /**
674 * The redirection triggered by the OAuth authentication server as response to the
675 * GET AUTHORIZATION, and deferred in {@link #onNewIntent(Intent)}, is processed here.
676 */
677 @Override
678 protected void onResume() {
679 super.onResume();
680
681 // bound here to avoid spurious changes triggered by Android on device rotations
682 mHostUrlInput.setOnFocusChangeListener(this);
683 mHostUrlInput.addTextChangedListener(mHostUrlInputWatcher);
684
685 if (mNewCapturedUriFromOAuth2Redirection != null) {
686 getOAuth2AccessTokenFromCapturedRedirection();
687 }
688
689 if (mOperationsServiceBinder != null) {
690 doOnResumeAndBound();
691 }
692
693 }
694
695
696 @Override
697 protected void onPause() {
698 if (mOperationsServiceBinder != null) {
699 mOperationsServiceBinder.removeOperationListener(this);
700 }
701
702 mHostUrlInput.removeTextChangedListener(mHostUrlInputWatcher);
703 mHostUrlInput.setOnFocusChangeListener(null);
704
705 super.onPause();
706 }
707
708 @Override
709 protected void onDestroy() {
710
711 mHostUrlInputWatcher = null;
712
713 if (mOperationsServiceConnection != null) {
714 unbindService(mOperationsServiceConnection);
715 mOperationsServiceBinder = null;
716 }
717 super.onDestroy();
718 }
719
720
721 /**
722 * Parses the redirection with the response to the GET AUTHORIZATION request to the
723 * oAuth server and requests for the access token (GET ACCESS TOKEN)
724 */
725 private void getOAuth2AccessTokenFromCapturedRedirection() {
726 /// Parse data from OAuth redirection
727 String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();
728 mNewCapturedUriFromOAuth2Redirection = null;
729
730 /// Showing the dialog with instructions for the user.
731 IndeterminateProgressDialog dialog =
732 IndeterminateProgressDialog.newInstance(R.string.auth_getting_authorization, true);
733 dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
734
735 /// GET ACCESS TOKEN to the oAuth server
736 Intent getServerInfoIntent = new Intent();
737 getServerInfoIntent.setAction(OperationsService.ACTION_OAUTH2_GET_ACCESS_TOKEN);
738
739 getServerInfoIntent.putExtra(
740 OperationsService.EXTRA_SERVER_URL,
741 mOAuthTokenEndpointText.getText().toString().trim());
742
743 getServerInfoIntent.putExtra(
744 OperationsService.EXTRA_OAUTH2_QUERY_PARAMETERS,
745 queryParameters);
746
747 if (mOperationsServiceBinder != null) {
748 //Log_OC.wtf(TAG, "getting access token..." );
749 mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
750 }
751 }
752
753
754
755 /**
756 * Handles the change of focus on the text inputs for the server URL and the password
757 */
758 public void onFocusChange(View view, boolean hasFocus) {
759 if (view.getId() == R.id.hostUrlInput) {
760 if (!hasFocus) {
761 onUrlInputFocusLost();
762 }
763 else {
764 showRefreshButton(false);
765 }
766
767 } else if (view.getId() == R.id.account_password) {
768 onPasswordFocusChanged(hasFocus);
769 }
770 }
771
772
773 /**
774 * Handles changes in focus on the text input for the server URL.
775 *
776 * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to
777 * other field. The operation to check the existence of the server in the entered URL is
778 * started.
779 *
780 * When hasFocus: user 'comes back' to write again the server URL.
781 */
782 private void onUrlInputFocusLost() {
783 if (!mServerInfo.mBaseUrl.equals(
784 normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) {
785 // check server again only if the user changed something in the field
786 checkOcServer();
787 } else {
788 mOkButton.setEnabled(mServerIsValid);
789 showRefreshButton(!mServerIsValid);
790 }
791 }
792
793
794 private void checkOcServer() {
795 String uri = mHostUrlInput.getText().toString().trim();
796 mServerIsValid = false;
797 mServerIsChecked = false;
798 mOkButton.setEnabled(false);
799 mServerInfo = new GetServerInfoOperation.ServerInfo();
800 showRefreshButton(false);
801
802 if (uri.length() != 0) {
803 // Handle internationalized domain names
804 uri = DisplayUtils.convertIdn(uri, true);
805
806 mServerStatusText = R.string.auth_testing_connection;
807 mServerStatusIcon = R.drawable.progress_small;
808 showServerStatus();
809
810 Intent getServerInfoIntent = new Intent();
811 getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO);
812 getServerInfoIntent.putExtra(
813 OperationsService.EXTRA_SERVER_URL,
814 normalizeUrlSuffix(uri)
815 );
816 if (mOperationsServiceBinder != null) {
817 mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
818 } else {
819 Log_OC.wtf(TAG, "Server check tried with OperationService unbound!" );
820 }
821
822 } else {
823 mServerStatusText = 0;
824 mServerStatusIcon = 0;
825 showServerStatus();
826 }
827 }
828
829
830 /**
831 * Handles changes in focus on the text input for the password (basic authorization).
832 *
833 * When (hasFocus), the button to toggle password visibility is shown.
834 *
835 * When (!hasFocus), the button is made invisible and the password is hidden.
836 *
837 * @param hasFocus 'True' if focus is received, 'false' if is lost
838 */
839 private void onPasswordFocusChanged(boolean hasFocus) {
840 if (hasFocus) {
841 showViewPasswordButton();
842 } else {
843 hidePassword();
844 hidePasswordButton();
845 }
846 }
847
848
849 private void showViewPasswordButton() {
850 int drawable = R.drawable.ic_view;
851 if (isPasswordVisible()) {
852 drawable = R.drawable.ic_hide;
853 }
854 mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0);
855 }
856
857 private boolean isPasswordVisible() {
858 return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) ==
859 InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
860 }
861
862 private void hidePasswordButton() {
863 mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
864 }
865
866 private void showPassword() {
867 mPasswordInput.setInputType(
868 InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
869 );
870 showViewPasswordButton();
871 }
872
873 private void hidePassword() {
874 mPasswordInput.setInputType(
875 InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
876 );
877 showViewPasswordButton();
878 }
879
880 /**
881 * Checks the credentials of the user in the root of the ownCloud server
882 * before creating a new local account.
883 *
884 * For basic authorization, a check of existence of the root folder is
885 * performed.
886 *
887 * For OAuth, starts the flow to get an access token; the credentials test
888 * is postponed until it is available.
889 *
890 * IMPORTANT ENTRY POINT 4
891 */
892 public void onOkClick() {
893 // this check should be unnecessary
894 if (mServerInfo.mVersion == null ||
895 !mServerInfo.mVersion.isVersionValid() ||
896 mServerInfo.mBaseUrl == null ||
897 mServerInfo.mBaseUrl.length() == 0) {
898 mServerStatusIcon = R.drawable.common_error;
899 mServerStatusText = R.string.auth_wtf_reenter_URL;
900 showServerStatus();
901 mOkButton.setEnabled(false);
902 return;
903 }
904
905 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).
906 equals(mAuthTokenType)) {
907
908 startOauthorization();
909 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).
910 equals(mAuthTokenType)) {
911
912 startSamlBasedFederatedSingleSignOnAuthorization();
913 } else {
914 checkBasicAuthorization();
915 }
916 }
917
918
919 /**
920 * Tests the credentials entered by the user performing a check of existence on
921 * the root folder of the ownCloud server.
922 */
923 private void checkBasicAuthorization() {
924 /// get basic credentials entered by user
925 String username = mUsernameInput.getText().toString();
926 String password = mPasswordInput.getText().toString();
927
928 /// be gentle with the user
929 IndeterminateProgressDialog dialog =
930 IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true);
931 dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
932
933 /// validate credentials accessing the root folder
934 OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username,
935 password);
936 accessRootFolder(credentials);
937 }
938
939 private void accessRootFolder(OwnCloudCredentials credentials) {
940 mAsyncTask = new AuthenticatorAsyncTask(this);
941 Object[] params = { mServerInfo.mBaseUrl, credentials };
942 mAsyncTask.execute(params);
943 }
944
945
946 /**
947 * Starts the OAuth 'grant type' flow to get an access token, with
948 * a GET AUTHORIZATION request to the BUILT-IN authorization server.
949 */
950 private void startOauthorization() {
951 // be gentle with the user
952 mAuthStatusIcon = R.drawable.progress_small;
953 mAuthStatusText = R.string.oauth_login_connection;
954 showAuthStatus();
955
956 // GET AUTHORIZATION request
957 Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim());
958 Uri.Builder uriBuilder = uri.buildUpon();
959 uriBuilder.appendQueryParameter(
960 OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)
961 );
962 uriBuilder.appendQueryParameter(
963 OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)
964 );
965 uriBuilder.appendQueryParameter(
966 OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)
967 );
968 uriBuilder.appendQueryParameter(
969 OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)
970 );
971 uri = uriBuilder.build();
972 Log_OC.d(TAG, "Starting browser to view " + uri.toString());
973 Intent i = new Intent(Intent.ACTION_VIEW, uri);
974 startActivity(i);
975 }
976
977
978 /**
979 * Starts the Web Single Sign On flow to get access to the root folder
980 * in the server.
981 */
982 private void startSamlBasedFederatedSingleSignOnAuthorization() {
983 /// be gentle with the user
984 mAuthStatusIcon = R.drawable.progress_small;
985 mAuthStatusText = R.string.auth_connecting_auth_server;
986 showAuthStatus();
987
988 /// Show SAML-based SSO web dialog
989 String targetUrl = mServerInfo.mBaseUrl
990 + AccountUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType);
991 SamlWebViewDialog dialog = SamlWebViewDialog.newInstance(targetUrl, targetUrl);
992 dialog.show(getSupportFragmentManager(), SAML_DIALOG_TAG);
993 }
994
995 /**
996 * Callback method invoked when a RemoteOperation executed by this Activity finishes.
997 *
998 * Dispatches the operation flow to the right method.
999 */
1000 @Override
1001 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
1002
1003 if (operation instanceof GetServerInfoOperation) {
1004 if (operation.hashCode() == mWaitingForOpId) {
1005 onGetServerInfoFinish(result);
1006 } // else nothing ; only the last check operation is considered;
1007 // multiple can be started if the user amends a URL quickly
1008
1009 } else if (operation instanceof OAuth2GetAccessToken) {
1010 onGetOAuthAccessTokenFinish(result);
1011
1012 } else if (operation instanceof GetRemoteUserNameOperation) {
1013 onGetUserNameFinish(result);
1014 }
1015
1016 }
1017
1018 private void onGetUserNameFinish(RemoteOperationResult result) {
1019 mWaitingForOpId = Long.MAX_VALUE;
1020 if (result.isSuccess()) {
1021 boolean success = false;
1022 String username = (String) result.getData().get(0);
1023
1024 if ( mAction == ACTION_CREATE) {
1025 mUsernameInput.setText(username);
1026 success = createAccount(result);
1027 } else {
1028
1029 if (!mUsernameInput.getText().toString().equals(username)) {
1030 // fail - not a new account, but an existing one; disallow
1031 result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME);
1032 mAuthToken = "";
1033 updateAuthStatusIconAndText(result);
1034 showAuthStatus();
1035 Log_OC.d(TAG, result.getLogMessage());
1036 } else {
1037 try {
1038 updateAccountAuthentication();
1039 success = true;
1040
1041 } catch (AccountNotFoundException e) {
1042 Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
1043 Toast.makeText(this, R.string.auth_account_does_not_exist,
1044 Toast.LENGTH_SHORT).show();
1045 finish();
1046 }
1047 }
1048 }
1049
1050 if (success)
1051 finish();
1052 } else {
1053 updateStatusIconFailUserName();
1054 showAuthStatus();
1055 Log_OC.e(TAG, "Access to user name failed: " + result.getLogMessage());
1056 }
1057
1058 }
1059
1060 /**
1061 * Processes the result of the server check performed when the user finishes the enter of the
1062 * server URL.
1063 *
1064 * @param result Result of the check.
1065 */
1066 private void onGetServerInfoFinish(RemoteOperationResult result) {
1067 /// update activity state
1068 mServerIsChecked = true;
1069 mWaitingForOpId = Long.MAX_VALUE;
1070
1071 // update server status, but don't show it yet
1072 updateServerStatusIconAndText(result);
1073
1074 if (result.isSuccess()) {
1075 /// SUCCESS means:
1076 // 1. connection succeeded, and we know if it's SSL or not
1077 // 2. server is installed
1078 // 3. we got the server version
1079 // 4. we got the authentication method required by the server
1080 mServerInfo = (GetServerInfoOperation.ServerInfo) (result.getData().get(0));
1081
1082 if (!authSupported(mServerInfo.mAuthMethod)) {
1083
1084 updateServerStatusIconNoRegularAuth(); // overrides updateServerStatusIconAndText()
1085 mServerIsValid = false;
1086
1087 } else {
1088 mServerIsValid = true;
1089 }
1090
1091 } else {
1092 mServerIsValid = false;
1093 }
1094
1095 // refresh UI
1096 showRefreshButton(!mServerIsValid);
1097 showServerStatus();
1098 mOkButton.setEnabled(mServerIsValid);
1099
1100 /// very special case (TODO: move to a common place for all the remote operations)
1101 if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
1102 showUntrustedCertDialog(result);
1103 }
1104 }
1105
1106
1107 private boolean authSupported(AuthenticationMethod authMethod) {
1108 return (( BASIC_TOKEN_TYPE.equals(mAuthTokenType) &&
1109 AuthenticationMethod.BASIC_HTTP_AUTH.equals(authMethod) ) ||
1110 ( OAUTH_TOKEN_TYPE.equals(mAuthTokenType) &&
1111 AuthenticationMethod.BEARER_TOKEN.equals(authMethod)) ||
1112 ( SAML_TOKEN_TYPE.equals(mAuthTokenType) &&
1113 AuthenticationMethod.SAML_WEB_SSO.equals(authMethod))
1114 );
1115 }
1116
1117
1118 // TODO remove, if possible
1119 private String normalizeUrl(String url, boolean sslWhenUnprefixed) {
1120 if (url != null && url.length() > 0) {
1121 url = url.trim();
1122 if (!url.toLowerCase().startsWith("http://") &&
1123 !url.toLowerCase().startsWith("https://")) {
1124 if (sslWhenUnprefixed) {
1125 url = "https://" + url;
1126 } else {
1127 url = "http://" + url;
1128 }
1129 }
1130
1131 url = normalizeUrlSuffix(url);
1132 }
1133 return (url != null ? url : "");
1134 }
1135
1136
1137 private String normalizeUrlSuffix(String url) {
1138 if (url.endsWith("/")) {
1139 url = url.substring(0, url.length() - 1);
1140 }
1141 url = trimUrlWebdav(url);
1142 return url;
1143 }
1144
1145
1146 // TODO remove, if possible
1147 private String trimUrlWebdav(String url){
1148 if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_4_0_AND_LATER)){
1149 url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_4_0_AND_LATER.length());
1150 }
1151 return url;
1152 }
1153
1154
1155 /**
1156 * Chooses the right icon and text to show to the user for the received operation result.
1157 *
1158 * @param result Result of a remote operation performed in this activity
1159 */
1160 private void updateServerStatusIconAndText(RemoteOperationResult result) {
1161 mServerStatusIcon = R.drawable.common_error; // the most common case in the switch below
1162
1163 switch (result.getCode()) {
1164 case OK_SSL:
1165 mServerStatusIcon = R.drawable.ic_lock;
1166 mServerStatusText = R.string.auth_secure_connection;
1167 break;
1168
1169 case OK_NO_SSL:
1170 case OK:
1171 if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) {
1172 mServerStatusText = R.string.auth_connection_established;
1173 mServerStatusIcon = R.drawable.ic_ok;
1174 } else {
1175 mServerStatusText = R.string.auth_nossl_plain_ok_title;
1176 mServerStatusIcon = R.drawable.ic_lock_open;
1177 }
1178 break;
1179
1180 case NO_NETWORK_CONNECTION:
1181 mServerStatusIcon = R.drawable.no_network;
1182 mServerStatusText = R.string.auth_no_net_conn_title;
1183 break;
1184
1185 case SSL_RECOVERABLE_PEER_UNVERIFIED:
1186 mServerStatusText = R.string.auth_ssl_unverified_server_title;
1187 break;
1188 case BAD_OC_VERSION:
1189 mServerStatusText = R.string.auth_bad_oc_version_title;
1190 break;
1191 case WRONG_CONNECTION:
1192 mServerStatusText = R.string.auth_wrong_connection_title;
1193 break;
1194 case TIMEOUT:
1195 mServerStatusText = R.string.auth_timeout_title;
1196 break;
1197 case INCORRECT_ADDRESS:
1198 mServerStatusText = R.string.auth_incorrect_address_title;
1199 break;
1200 case SSL_ERROR:
1201 mServerStatusText = R.string.auth_ssl_general_error_title;
1202 break;
1203 case UNAUTHORIZED:
1204 mServerStatusText = R.string.auth_unauthorized;
1205 break;
1206 case HOST_NOT_AVAILABLE:
1207 mServerStatusText = R.string.auth_unknown_host_title;
1208 break;
1209 case INSTANCE_NOT_CONFIGURED:
1210 mServerStatusText = R.string.auth_not_configured_title;
1211 break;
1212 case FILE_NOT_FOUND:
1213 mServerStatusText = R.string.auth_incorrect_path_title;
1214 break;
1215 case OAUTH2_ERROR:
1216 mServerStatusText = R.string.auth_oauth_error;
1217 break;
1218 case OAUTH2_ERROR_ACCESS_DENIED:
1219 mServerStatusText = R.string.auth_oauth_error_access_denied;
1220 break;
1221 case UNHANDLED_HTTP_CODE:
1222 case UNKNOWN_ERROR:
1223 mServerStatusText = R.string.auth_unknown_error_title;
1224 break;
1225 case OK_REDIRECT_TO_NON_SECURE_CONNECTION:
1226 mServerStatusIcon = R.drawable.ic_lock_open;
1227 mServerStatusText = R.string.auth_redirect_non_secure_connection_title;
1228 break;
1229 default:
1230 mServerStatusText = 0;
1231 mServerStatusIcon = 0;
1232 }
1233 }
1234
1235
1236 /**
1237 * Chooses the right icon and text to show to the user for the received operation result.
1238 *
1239 * @param result Result of a remote operation performed in this activity
1240 */
1241 private void updateAuthStatusIconAndText(RemoteOperationResult result) {
1242 mAuthStatusIcon = R.drawable.common_error; // the most common case in the switch below
1243
1244 switch (result.getCode()) {
1245 case OK_SSL:
1246 mAuthStatusIcon = R.drawable.ic_lock;
1247 mAuthStatusText = R.string.auth_secure_connection;
1248 break;
1249
1250 case OK_NO_SSL:
1251 case OK:
1252 if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) {
1253 mAuthStatusText = R.string.auth_connection_established;
1254 mAuthStatusIcon = R.drawable.ic_ok;
1255 } else {
1256 mAuthStatusText = R.string.auth_nossl_plain_ok_title;
1257 mAuthStatusIcon = R.drawable.ic_lock_open;
1258 }
1259 break;
1260
1261 case NO_NETWORK_CONNECTION:
1262 mAuthStatusIcon = R.drawable.no_network;
1263 mAuthStatusText = R.string.auth_no_net_conn_title;
1264 break;
1265
1266 case SSL_RECOVERABLE_PEER_UNVERIFIED:
1267 mAuthStatusText = R.string.auth_ssl_unverified_server_title;
1268 break;
1269 case BAD_OC_VERSION:
1270 mAuthStatusText = R.string.auth_bad_oc_version_title;
1271 break;
1272 case WRONG_CONNECTION:
1273 mAuthStatusText = R.string.auth_wrong_connection_title;
1274 break;
1275 case TIMEOUT:
1276 mAuthStatusText = R.string.auth_timeout_title;
1277 break;
1278 case INCORRECT_ADDRESS:
1279 mAuthStatusText = R.string.auth_incorrect_address_title;
1280 break;
1281 case SSL_ERROR:
1282 mAuthStatusText = R.string.auth_ssl_general_error_title;
1283 break;
1284 case UNAUTHORIZED:
1285 mAuthStatusText = R.string.auth_unauthorized;
1286 break;
1287 case HOST_NOT_AVAILABLE:
1288 mAuthStatusText = R.string.auth_unknown_host_title;
1289 break;
1290 case INSTANCE_NOT_CONFIGURED:
1291 mAuthStatusText = R.string.auth_not_configured_title;
1292 break;
1293 case FILE_NOT_FOUND:
1294 mAuthStatusText = R.string.auth_incorrect_path_title;
1295 break;
1296 case OAUTH2_ERROR:
1297 mAuthStatusText = R.string.auth_oauth_error;
1298 break;
1299 case OAUTH2_ERROR_ACCESS_DENIED:
1300 mAuthStatusText = R.string.auth_oauth_error_access_denied;
1301 break;
1302 case ACCOUNT_NOT_NEW:
1303 mAuthStatusText = R.string.auth_account_not_new;
1304 break;
1305 case ACCOUNT_NOT_THE_SAME:
1306 mAuthStatusText = R.string.auth_account_not_the_same;
1307 break;
1308 case UNHANDLED_HTTP_CODE:
1309 case UNKNOWN_ERROR:
1310 mAuthStatusText = R.string.auth_unknown_error_title;
1311 break;
1312 default:
1313 mAuthStatusText = 0;
1314 mAuthStatusIcon = 0;
1315 }
1316 }
1317
1318
1319 private void updateStatusIconFailUserName(){
1320 mAuthStatusIcon = R.drawable.common_error;
1321 mAuthStatusText = R.string.auth_fail_get_user_name;
1322 }
1323
1324 private void updateServerStatusIconNoRegularAuth(){
1325 mServerStatusIcon = R.drawable.common_error;
1326 mServerStatusText = R.string.auth_can_not_auth_against_server;
1327 }
1328
1329 /**
1330 * Processes the result of the request for and access token send
1331 * to an OAuth authorization server.
1332 *
1333 * @param result Result of the operation.
1334 */
1335 private void onGetOAuthAccessTokenFinish(RemoteOperationResult result) {
1336 mWaitingForOpId = Long.MAX_VALUE;
1337 dismissDialog(WAIT_DIALOG_TAG);
1338
1339 if (result.isSuccess()) {
1340 /// be gentle with the user
1341 IndeterminateProgressDialog dialog =
1342 IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true);
1343 dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG);
1344
1345 /// time to test the retrieved access token on the ownCloud server
1346 @SuppressWarnings("unchecked")
1347 Map<String, String> tokens = (Map<String, String>)(result.getData().get(0));
1348 mAuthToken = tokens.get(OAuth2Constants.KEY_ACCESS_TOKEN);
1349 Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken);
1350
1351 /// validate token accessing to root folder / getting session
1352 OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBearerCredentials(
1353 mAuthToken);
1354 accessRootFolder(credentials);
1355
1356 } else {
1357 updateAuthStatusIconAndText(result);
1358 showAuthStatus();
1359 Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
1360 }
1361 }
1362
1363
1364 /**
1365 * Processes the result of the access check performed to try the user credentials.
1366 *
1367 * Creates a new account through the AccountManager.
1368 *
1369 * @param result Result of the operation.
1370 */
1371 @Override
1372 public void onAuthenticatorTaskCallback(RemoteOperationResult result) {
1373 mWaitingForOpId = Long.MAX_VALUE;
1374 dismissDialog(WAIT_DIALOG_TAG);
1375
1376 if (result.isSuccess()) {
1377 Log_OC.d(TAG, "Successful access - time to save the account");
1378
1379 boolean success = false;
1380
1381 if (mAction == ACTION_CREATE) {
1382 success = createAccount(result);
1383
1384 } else {
1385 try {
1386 updateAccountAuthentication();
1387 success = true;
1388
1389 } catch (AccountNotFoundException e) {
1390 Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
1391 Toast.makeText(this, R.string.auth_account_does_not_exist,
1392 Toast.LENGTH_SHORT).show();
1393 finish();
1394 }
1395 }
1396
1397 if (success) {
1398 finish();
1399 }
1400
1401 } else if (result.isServerFail() || result.isException()) {
1402 /// server errors or exceptions in authorization take to requiring a new check of
1403 /// the server
1404 mServerIsChecked = true;
1405 mServerIsValid = false;
1406 mServerInfo = new GetServerInfoOperation.ServerInfo();
1407
1408 // update status icon and text
1409 updateServerStatusIconAndText(result);
1410 showServerStatus();
1411 mAuthStatusIcon = 0;
1412 mAuthStatusText = 0;
1413 showAuthStatus();
1414
1415 // update input controls state
1416 showRefreshButton(true);
1417 mOkButton.setEnabled(false);
1418
1419 // very special case (TODO: move to a common place for all the remote operations)
1420 if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
1421 showUntrustedCertDialog(result);
1422 }
1423
1424 } else { // authorization fail due to client side - probably wrong credentials
1425 updateAuthStatusIconAndText(result);
1426 showAuthStatus();
1427 Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
1428 }
1429 }
1430
1431
1432
1433
1434 /**
1435 * Updates the authentication token.
1436 *
1437 * Sets the proper response so that the AccountAuthenticator that started this activity
1438 * saves a new authorization token for mAccount.
1439 *
1440 * Kills the session kept by OwnCloudClientManager so that a new one will created with
1441 * the new credentials when needed.
1442 */
1443 private void updateAccountAuthentication() throws AccountNotFoundException {
1444
1445 Bundle response = new Bundle();
1446 response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
1447 response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
1448
1449 if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).
1450 equals(mAuthTokenType)) {
1451 response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
1452 // the next line is necessary, notifications are calling directly to the
1453 // AuthenticatorActivity to update, without AccountManager intervention
1454 mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
1455
1456 } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).
1457 equals(mAuthTokenType)) {
1458
1459 response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
1460 // the next line is necessary; by now, notifications are calling directly to the
1461 // AuthenticatorActivity to update, without AccountManager intervention
1462 mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
1463
1464 } else {
1465 response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
1466 mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
1467 }
1468 setAccountAuthenticatorResult(response);
1469
1470 }
1471
1472
1473 /**
1474 * Creates a new account through the Account Authenticator that started this activity.
1475 *
1476 * This makes the account permanent.
1477 *
1478 * TODO Decide how to name the OAuth accounts
1479 */
1480 private boolean createAccount(RemoteOperationResult authResult) {
1481 /// create and save new ownCloud account
1482 boolean isOAuth = AccountTypeUtils.
1483 getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType);
1484 boolean isSaml = AccountTypeUtils.
1485 getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType);
1486
1487 String lastPermanentLocation = authResult.getLastPermanentLocation();
1488 if (lastPermanentLocation != null) {
1489 mServerInfo.mBaseUrl = AccountUtils.trimWebdavSuffix(lastPermanentLocation);
1490 }
1491
1492 Uri uri = Uri.parse(mServerInfo.mBaseUrl);
1493 String username = mUsernameInput.getText().toString().trim();
1494 if (isOAuth) {
1495 username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
1496 }
1497 String accountName = com.owncloud.android.lib.common.accounts.AccountUtils.
1498 buildAccountName(uri, username);
1499 Account newAccount = new Account(accountName, MainApp.getAccountType());
1500 if (AccountUtils.exists(newAccount, getApplicationContext())) {
1501 // fail - not a new account, but an existing one; disallow
1502 RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW);
1503 updateAuthStatusIconAndText(result);
1504 showAuthStatus();
1505 Log_OC.d(TAG, result.getLogMessage());
1506 return false;
1507
1508 } else {
1509 mAccount = newAccount;
1510
1511 if (isOAuth || isSaml) {
1512 // with external authorizations, the password is never input in the app
1513 mAccountMgr.addAccountExplicitly(mAccount, "", null);
1514 } else {
1515 mAccountMgr.addAccountExplicitly(
1516 mAccount, mPasswordInput.getText().toString(), null
1517 );
1518 }
1519
1520 // include account version with the new account
1521 mAccountMgr.setUserData(
1522 mAccount,
1523 Constants.KEY_OC_ACCOUNT_VERSION,
1524 Integer.toString(AccountUtils.ACCOUNT_VERSION)
1525 );
1526
1527 /// add the new account as default in preferences, if there is none already
1528 Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);
1529 if (defaultAccount == null) {
1530 SharedPreferences.Editor editor = PreferenceManager
1531 .getDefaultSharedPreferences(this).edit();
1532 editor.putString("select_oc_account", accountName);
1533 editor.commit();
1534 }
1535
1536 /// prepare result to return to the Authenticator
1537 // TODO check again what the Authenticator makes with it; probably has the same
1538 // effect as addAccountExplicitly, but it's not well done
1539 final Intent intent = new Intent();
1540 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType());
1541 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
1542 intent.putExtra(AccountManager.KEY_USERDATA, username);
1543 if (isOAuth || isSaml) {
1544 mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken);
1545 }
1546 /// add user data to the new account; TODO probably can be done in the last parameter
1547 // addAccountExplicitly, or in KEY_USERDATA
1548 mAccountMgr.setUserData(
1549 mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion()
1550 );
1551 mAccountMgr.setUserData(
1552 mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl
1553 );
1554
1555 if (isSaml) {
1556 mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE");
1557 } else if (isOAuth) {
1558 mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2, "TRUE");
1559 }
1560
1561 setAccountAuthenticatorResult(intent.getExtras());
1562 setResult(RESULT_OK, intent);
1563
1564 return true;
1565 }
1566 }
1567
1568
1569 /**
1570 * Starts and activity to open the 'new account' page in the ownCloud web site
1571 *
1572 * @param view 'Account register' button
1573 */
1574 public void onRegisterClick(View view) {
1575 Intent register = new Intent(
1576 Intent.ACTION_VIEW, Uri.parse(getString(R.string.welcome_link_url))
1577 );
1578 setResult(RESULT_CANCELED);
1579 startActivity(register);
1580 }
1581
1582
1583 /**
1584 * Updates the content and visibility state of the icon and text associated
1585 * to the last check on the ownCloud server.
1586 *
1587 */
1588 private void showServerStatus() {
1589 if (mServerStatusIcon == 0 && mServerStatusText == 0) {
1590 mServerStatusView.setVisibility(View.INVISIBLE);
1591
1592 } else {
1593 mServerStatusView.setText(mServerStatusText);
1594 mServerStatusView.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon, 0, 0, 0);
1595 mServerStatusView.setVisibility(View.VISIBLE);
1596 }
1597
1598 }
1599
1600
1601 /**
1602 * Updates the content and visibility state of the icon and text associated
1603 * to the interactions with the OAuth authorization server.
1604 */
1605 private void showAuthStatus() {
1606 if (mAuthStatusIcon == 0 && mAuthStatusText == 0) {
1607 mAuthStatusView.setVisibility(View.INVISIBLE);
1608
1609 } else {
1610 mAuthStatusView.setText(mAuthStatusText);
1611 mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
1612 mAuthStatusView.setVisibility(View.VISIBLE);
1613 }
1614 }
1615
1616
1617 private void showRefreshButton (boolean show) {
1618 if (show) {
1619 mRefreshButton.setVisibility(View.VISIBLE);
1620 } else {
1621 mRefreshButton.setVisibility(View.GONE);
1622 }
1623 }
1624
1625 /**
1626 * Called when the eye icon in the password field is clicked.
1627 *
1628 * Toggles the visibility of the password in the field.
1629 */
1630 public void onViewPasswordClick() {
1631 int selectionStart = mPasswordInput.getSelectionStart();
1632 int selectionEnd = mPasswordInput.getSelectionEnd();
1633 if (isPasswordVisible()) {
1634 hidePassword();
1635 } else {
1636 showPassword();
1637 }
1638 mPasswordInput.setSelection(selectionStart, selectionEnd);
1639 }
1640
1641
1642 /**
1643 * Called when the checkbox for OAuth authorization is clicked.
1644 *
1645 * Hides or shows the input fields for user & password.
1646 *
1647 * @param view 'View password' 'button'
1648 */
1649 public void onCheckClick(View view) {
1650 CheckBox oAuth2Check = (CheckBox)view;
1651 if (oAuth2Check.isChecked()) {
1652 mAuthTokenType = OAUTH_TOKEN_TYPE;
1653 } else {
1654 mAuthTokenType = BASIC_TOKEN_TYPE;
1655 }
1656 updateAuthenticationPreFragmentVisibility();
1657 }
1658
1659
1660 /**
1661 * Called when the 'action' button in an IME is pressed ('enter' in software keyboard).
1662 *
1663 * Used to trigger the authentication check when the user presses 'enter' after writing the
1664 * password, or to throw the server test when the only field on screen is the URL input field.
1665 */
1666 @Override
1667 public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) {
1668 if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null &&
1669 inputField.equals(mPasswordInput)) {
1670 if (mOkButton.isEnabled()) {
1671 mOkButton.performClick();
1672 }
1673
1674 } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null &&
1675 inputField.equals(mHostUrlInput)) {
1676 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).
1677 equals(mAuthTokenType)) {
1678 checkOcServer();
1679 }
1680 }
1681 return false; // always return false to grant that the software keyboard is hidden anyway
1682 }
1683
1684
1685 private abstract static class RightDrawableOnTouchListener implements OnTouchListener {
1686
1687 private int fuzz = 75;
1688
1689 /**
1690 * {@inheritDoc}
1691 */
1692 @Override
1693 public boolean onTouch(View view, MotionEvent event) {
1694 Drawable rightDrawable = null;
1695 if (view instanceof TextView) {
1696 Drawable[] drawables = ((TextView)view).getCompoundDrawables();
1697 if (drawables.length > 2) {
1698 rightDrawable = drawables[2];
1699 }
1700 }
1701 if (rightDrawable != null) {
1702 final int x = (int) event.getX();
1703 final int y = (int) event.getY();
1704 final Rect bounds = rightDrawable.getBounds();
1705 if ( x >= (view.getRight() - bounds.width() - fuzz) &&
1706 x <= (view.getRight() - view.getPaddingRight() + fuzz) &&
1707 y >= (view.getPaddingTop() - fuzz) &&
1708 y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
1709
1710 return onDrawableTouch(event);
1711 }
1712 }
1713 return false;
1714 }
1715
1716 public abstract boolean onDrawableTouch(final MotionEvent event);
1717 }
1718
1719
1720 private void getRemoteUserNameOperation(String sessionCookie) {
1721
1722 Intent getUserNameIntent = new Intent();
1723 getUserNameIntent.setAction(OperationsService.ACTION_GET_USER_NAME);
1724 getUserNameIntent.putExtra(OperationsService.EXTRA_SERVER_URL, mServerInfo.mBaseUrl);
1725 getUserNameIntent.putExtra(OperationsService.EXTRA_COOKIE, sessionCookie);
1726
1727 if (mOperationsServiceBinder != null) {
1728 mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);
1729 }
1730 }
1731
1732
1733 @Override
1734 public void onSsoFinished(String sessionCookie) {
1735 if (sessionCookie != null && sessionCookie.length() > 0) {
1736 Log_OC.d(TAG, "Successful SSO - time to save the account");
1737 mAuthToken = sessionCookie;
1738 getRemoteUserNameOperation(sessionCookie);
1739 Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG);
1740 if (fd != null && fd instanceof DialogFragment) {
1741 Dialog d = ((DialogFragment)fd).getDialog();
1742 if (d != null && d.isShowing()) {
1743 d.dismiss();
1744 }
1745 }
1746
1747 } else {
1748 // TODO - show fail
1749 Log_OC.d(TAG, "SSO failed");
1750 }
1751
1752 }
1753
1754 @Override
1755 public boolean onTouchEvent(MotionEvent event) {
1756 if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).
1757 equals(mAuthTokenType) &&
1758 mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) {
1759 checkOcServer();
1760 }
1761 return super.onTouchEvent(event);
1762 }
1763
1764
1765 /**
1766 * Show untrusted cert dialog
1767 */
1768 public void showUntrustedCertDialog(
1769 X509Certificate x509Certificate, SslError error, SslErrorHandler handler
1770 ) {
1771 // Show a dialog with the certificate info
1772 SslUntrustedCertDialog dialog;
1773 if (x509Certificate == null) {
1774 dialog = SslUntrustedCertDialog.newInstanceForEmptySslError(error, handler);
1775 } else {
1776 dialog = SslUntrustedCertDialog.
1777 newInstanceForFullSslError(x509Certificate, error, handler);
1778 }
1779 FragmentManager fm = getSupportFragmentManager();
1780 FragmentTransaction ft = fm.beginTransaction();
1781 ft.addToBackStack(null);
1782 dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
1783 }
1784
1785
1786 /**
1787 * Show untrusted cert dialog
1788 */
1789 private void showUntrustedCertDialog(RemoteOperationResult result) {
1790 // Show a dialog with the certificate info
1791 SslUntrustedCertDialog dialog = SslUntrustedCertDialog.
1792 newInstanceForFullSslError((CertificateCombinedException)result.getException());
1793 FragmentManager fm = getSupportFragmentManager();
1794 FragmentTransaction ft = fm.beginTransaction();
1795 ft.addToBackStack(null);
1796 dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
1797
1798 }
1799
1800 /**
1801 * Called from SslValidatorDialog when a new server certificate was correctly saved.
1802 */
1803 public void onSavedCertificate() {
1804 Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG);
1805 if (fd == null) {
1806 // if SAML dialog is not shown,
1807 // the SslDialog was shown due to an SSL error in the server check
1808 checkOcServer();
1809 }
1810 }
1811
1812 /**
1813 * Called from SslValidatorDialog when a new server certificate could not be saved
1814 * when the user requested it.
1815 */
1816 @Override
1817 public void onFailedSavingCertificate() {
1818 dismissDialog(SAML_DIALOG_TAG);
1819 Toast.makeText(this, R.string.ssl_validator_not_saved, Toast.LENGTH_LONG).show();
1820 }
1821
1822 @Override
1823 public void onCancelCertificate() {
1824 dismissDialog(SAML_DIALOG_TAG);
1825 }
1826
1827
1828 private void doOnResumeAndBound() {
1829 //Log_OC.wtf(TAG, "registering to listen for operation callbacks" );
1830 mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler);
1831 if (mWaitingForOpId <= Integer.MAX_VALUE) {
1832 mOperationsServiceBinder.dispatchResultIfFinished((int)mWaitingForOpId, this);
1833 }
1834
1835 if (mPendingAutoCheck) {
1836 checkOcServer();
1837 }
1838 }
1839
1840
1841 private void dismissDialog(String dialogTag){
1842 Fragment frag = getSupportFragmentManager().findFragmentByTag(dialogTag);
1843 if (frag != null && frag instanceof DialogFragment) {
1844 DialogFragment dialog = (DialogFragment) frag;
1845 dialog.dismiss();
1846 }
1847 }
1848
1849
1850 /**
1851 * Implements callback methods for service binding.
1852 */
1853 private class OperationsServiceConnection implements ServiceConnection {
1854
1855 @Override
1856 public void onServiceConnected(ComponentName component, IBinder service) {
1857 if (component.equals(
1858 new ComponentName(AuthenticatorActivity.this, OperationsService.class)
1859 )) {
1860 mOperationsServiceBinder = (OperationsServiceBinder) service;
1861
1862 doOnResumeAndBound();
1863
1864 }
1865
1866 }
1867
1868 @Override
1869 public void onServiceDisconnected(ComponentName component) {
1870 if (component.equals(
1871 new ComponentName(AuthenticatorActivity.this, OperationsService.class)
1872 )) {
1873 Log_OC.e(TAG, "Operations service crashed");
1874 mOperationsServiceBinder = null;
1875 }
1876 }
1877
1878 }
1879
1880 /**
1881 * Create and show dialog for request authentication to the user
1882 * @param webView Web view to emebd into the authentication dialog.
1883 * @param handler Object responsible for catching and recovering HTTP authentication fails.
1884 */
1885 public void createAuthenticationDialog(WebView webView, HttpAuthHandler handler) {
1886
1887 // Show a dialog with the certificate info
1888 CredentialsDialogFragment dialog =
1889 CredentialsDialogFragment.newInstanceForCredentials(webView, handler);
1890 FragmentManager fm = getSupportFragmentManager();
1891 FragmentTransaction ft = fm.beginTransaction();
1892 ft.addToBackStack(null);
1893 dialog.setCancelable(false);
1894 dialog.show(ft, CREDENTIALS_DIALOG_TAG);
1895
1896 if (!mIsFirstAuthAttempt) {
1897 Toast.makeText(
1898 getApplicationContext(),
1899 getText(R.string.saml_authentication_wrong_pass),
1900 Toast.LENGTH_LONG
1901 ).show();
1902 } else {
1903 mIsFirstAuthAttempt = false;
1904 }
1905 }
1906
1907 /**
1908 * For retrieving the clicking on authentication cancel button
1909 */
1910 public void doNegativeAuthenticatioDialogClick(){
1911 mIsFirstAuthAttempt = true;
1912 }
1913
1914 }