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