AuthenticatorActivity.java 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author David A. Velasco
  6. * @author masensio
  7. * @author Mario Danic
  8. * Copyright (C) 2012 Bartek Przybylski
  9. * Copyright (C) 2015 ownCloud Inc.
  10. * Copyright (C) 2017 Mario Danic
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License version 2,
  14. * as published by the Free Software Foundation.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. *
  24. * All changes by Mario Danic are distributed under the following terms:
  25. *
  26. * This program is free software: you can redistribute it and/or modify
  27. * it under the terms of the GNU Affero General Public License as published by
  28. * the Free Software Foundation, either version 3 of the License, or
  29. * at your option) any later version.
  30. *
  31. * This program is distributed in the hope that it will be useful,
  32. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  33. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  34. * GNU Affero General Public License for more details.
  35. *
  36. * You should have received a copy of the GNU Affero General Public License
  37. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  38. *
  39. */
  40. package com.owncloud.android.authentication;
  41. import android.Manifest;
  42. import android.accounts.Account;
  43. import android.accounts.AccountManager;
  44. import android.annotation.SuppressLint;
  45. import android.content.ComponentName;
  46. import android.content.Context;
  47. import android.content.Intent;
  48. import android.content.ServiceConnection;
  49. import android.content.SharedPreferences;
  50. import android.content.pm.ActivityInfo;
  51. import android.content.pm.PackageManager;
  52. import android.graphics.Rect;
  53. import android.graphics.drawable.Drawable;
  54. import android.net.Uri;
  55. import android.net.http.SslCertificate;
  56. import android.net.http.SslError;
  57. import android.os.Build;
  58. import android.os.Bundle;
  59. import android.os.Handler;
  60. import android.os.IBinder;
  61. import android.preference.PreferenceManager;
  62. import android.text.Editable;
  63. import android.text.InputType;
  64. import android.text.TextUtils;
  65. import android.text.TextWatcher;
  66. import android.util.AndroidRuntimeException;
  67. import android.view.KeyEvent;
  68. import android.view.MotionEvent;
  69. import android.view.View;
  70. import android.view.View.OnFocusChangeListener;
  71. import android.view.View.OnTouchListener;
  72. import android.view.inputmethod.EditorInfo;
  73. import android.webkit.CookieManager;
  74. import android.webkit.CookieSyncManager;
  75. import android.webkit.HttpAuthHandler;
  76. import android.webkit.SslErrorHandler;
  77. import android.webkit.WebView;
  78. import android.webkit.WebViewClient;
  79. import android.widget.EditText;
  80. import android.widget.ImageButton;
  81. import android.widget.ProgressBar;
  82. import android.widget.TextView;
  83. import android.widget.TextView.OnEditorActionListener;
  84. import com.blikoon.qrcodescanner.QrCodeActivity;
  85. import com.google.android.material.snackbar.Snackbar;
  86. import com.google.android.material.textfield.TextInputLayout;
  87. import com.nextcloud.client.account.UserAccountManager;
  88. import com.nextcloud.client.device.DeviceInfo;
  89. import com.nextcloud.client.di.Injectable;
  90. import com.nextcloud.client.onboarding.FirstRunActivity;
  91. import com.nextcloud.client.onboarding.OnboardingService;
  92. import com.nextcloud.client.preferences.AppPreferences;
  93. import com.owncloud.android.MainApp;
  94. import com.owncloud.android.R;
  95. import com.owncloud.android.lib.common.OwnCloudAccount;
  96. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
  97. import com.owncloud.android.lib.common.OwnCloudCredentials;
  98. import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
  99. import com.owncloud.android.lib.common.UserInfo;
  100. import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
  101. import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
  102. import com.owncloud.android.lib.common.network.CertificateCombinedException;
  103. import com.owncloud.android.lib.common.network.NetworkUtils;
  104. import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
  105. import com.owncloud.android.lib.common.operations.RemoteOperation;
  106. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  107. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  108. import com.owncloud.android.lib.common.utils.Log_OC;
  109. import com.owncloud.android.lib.resources.status.OwnCloudVersion;
  110. import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
  111. import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod;
  112. import com.owncloud.android.operations.GetServerInfoOperation;
  113. import com.owncloud.android.services.OperationsService;
  114. import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
  115. import com.owncloud.android.ui.activity.FileDisplayActivity;
  116. import com.owncloud.android.ui.components.CustomEditText;
  117. import com.owncloud.android.ui.dialog.CredentialsDialogFragment;
  118. import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
  119. import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
  120. import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
  121. import com.owncloud.android.utils.DisplayUtils;
  122. import com.owncloud.android.utils.ErrorMessageAdapter;
  123. import com.owncloud.android.utils.PermissionUtil;
  124. import java.io.ByteArrayInputStream;
  125. import java.io.InputStream;
  126. import java.net.URLDecoder;
  127. import java.security.cert.Certificate;
  128. import java.security.cert.CertificateException;
  129. import java.security.cert.CertificateFactory;
  130. import java.security.cert.X509Certificate;
  131. import java.util.ArrayList;
  132. import java.util.HashMap;
  133. import java.util.Locale;
  134. import java.util.Map;
  135. import javax.inject.Inject;
  136. import androidx.annotation.NonNull;
  137. import androidx.annotation.Nullable;
  138. import androidx.fragment.app.DialogFragment;
  139. import androidx.fragment.app.Fragment;
  140. import androidx.fragment.app.FragmentManager;
  141. import androidx.fragment.app.FragmentTransaction;
  142. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  143. /**
  144. * This Activity is used to add an ownCloud account to the App
  145. */
  146. public class AuthenticatorActivity extends AccountAuthenticatorActivity
  147. implements OnRemoteOperationListener, OnFocusChangeListener, OnEditorActionListener, OnSslUntrustedCertListener,
  148. AuthenticatorAsyncTask.OnAuthenticatorTaskListener, Injectable {
  149. private static final String TAG = AuthenticatorActivity.class.getSimpleName();
  150. public static final String EXTRA_ACTION = "ACTION";
  151. public static final String EXTRA_ACCOUNT = "ACCOUNT";
  152. public static final String EXTRA_USE_PROVIDER_AS_WEBLOGIN = "USE_PROVIDER_AS_WEBLOGIN";
  153. private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT";
  154. private static final String KEY_OC_VERSION = "OC_VERSION";
  155. private static final String KEY_SERVER_VALID = "SERVER_VALID";
  156. private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED";
  157. private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT";
  158. private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON";
  159. private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN";
  160. private static final String KEY_PASSWORD_EXPOSED = "PASSWORD_VISIBLE";
  161. private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT";
  162. private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON";
  163. private static final String KEY_SERVER_AUTH_METHOD = "SERVER_AUTH_METHOD";
  164. private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID";
  165. private static final String KEY_AUTH_TOKEN = "AUTH_TOKEN";
  166. public static final byte ACTION_CREATE = 0;
  167. public static final byte ACTION_UPDATE_EXPIRED_TOKEN = 2; // detected by the app
  168. private static final String UNTRUSTED_CERT_DIALOG_TAG = "UNTRUSTED_CERT_DIALOG";
  169. private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG";
  170. private static final String CREDENTIALS_DIALOG_TAG = "CREDENTIALS_DIALOG";
  171. private static final String KEY_AUTH_IS_FIRST_ATTEMPT_TAG = "KEY_AUTH_IS_FIRST_ATTEMPT";
  172. private static final String KEY_USERNAME = "USERNAME";
  173. private static final String KEY_PASSWORD = "PASSWORD";
  174. private static final String KEY_ASYNC_TASK_IN_PROGRESS = "AUTH_IN_PROGRESS";
  175. private static final String WEB_LOGIN = "/index.php/login/flow";
  176. public static final String PROTOCOL_SUFFIX = "://";
  177. public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":";
  178. public static final String HTTPS_PROTOCOL = "https://";
  179. public static final String HTTP_PROTOCOL = "http://";
  180. public static final String REGULAR_SERVER_INPUT_TYPE = "regular";
  181. public static final String SUBDOMAIN_SERVER_INPUT_TYPE = "prefix";
  182. public static final String DIRECTORY_SERVER_INPUT_TYPE = "suffix";
  183. public static final int NO_ICON = 0;
  184. public static final String EMPTY_STRING = "";
  185. private static final int REQUEST_CODE_QR_SCAN = 101;
  186. public static final int REQUEST_CODE_FIRST_RUN = 102;
  187. /// parameters from EXTRAs in starter Intent
  188. private byte mAction;
  189. private Account mAccount;
  190. /// activity-level references / state
  191. private final Handler mHandler = new Handler();
  192. private ServiceConnection mOperationsServiceConnection;
  193. private OperationsServiceBinder mOperationsServiceBinder;
  194. private AccountManager mAccountMgr;
  195. /// Server PRE-Fragment elements
  196. private CustomEditText mHostUrlInput;
  197. private View mRefreshButton;
  198. private TextView mServerStatusView;
  199. private TextWatcher mHostUrlInputWatcher;
  200. private String mServerStatusText = EMPTY_STRING;
  201. private int mServerStatusIcon;
  202. private boolean mServerIsChecked;
  203. private boolean mServerIsValid;
  204. private GetServerInfoOperation.ServerInfo mServerInfo = new GetServerInfoOperation.ServerInfo();
  205. /// Authentication PRE-Fragment elements
  206. private EditText mUsernameInput;
  207. private EditText mPasswordInput;
  208. private View mOkButton;
  209. private TextView mAuthStatusView;
  210. private ImageButton mTestServerButton;
  211. private WebView mLoginWebView;
  212. private String mAuthStatusText = EMPTY_STRING;
  213. private int mAuthStatusIcon;
  214. private String mAuthToken = EMPTY_STRING;
  215. private AuthenticatorAsyncTask mAsyncTask;
  216. private boolean mIsFirstAuthAttempt;
  217. /// Identifier of operation in progress which result shouldn't be lost
  218. private long mWaitingForOpId = Long.MAX_VALUE;
  219. private boolean webViewLoginMethod;
  220. private String webViewUser;
  221. private String webViewPassword;
  222. private TextInputLayout mUsernameInputLayout;
  223. private TextInputLayout mPasswordInputLayout;
  224. private boolean forceOldLoginMethod;
  225. @Inject UserAccountManager accountManager;
  226. @Inject AppPreferences preferences;
  227. @Inject OnboardingService onboarding;
  228. @Inject DeviceInfo deviceInfo;
  229. /**
  230. * {@inheritDoc}
  231. *
  232. * IMPORTANT ENTRY POINT 1: activity is shown to the user
  233. */
  234. @Override
  235. protected void onCreate(Bundle savedInstanceState) {
  236. //Log_OC.e(TAG, "onCreate init");
  237. super.onCreate(savedInstanceState);
  238. Uri data = getIntent().getData();
  239. boolean directLogin = data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme));
  240. if (savedInstanceState == null && !directLogin) {
  241. onboarding.launchFirstRunIfNeeded(this);
  242. }
  243. // delete cookies for webView
  244. deleteCookies();
  245. // Workaround, for fixing a problem with Android Library Support v7 19
  246. //getWindow().requestFeature(Window.FEATURE_NO_TITLE);
  247. if (getSupportActionBar() != null) {
  248. getSupportActionBar().hide();
  249. getSupportActionBar().setDisplayHomeAsUpEnabled(false);
  250. getSupportActionBar().setDisplayShowHomeEnabled(false);
  251. getSupportActionBar().setDisplayShowTitleEnabled(false);
  252. }
  253. mIsFirstAuthAttempt = true;
  254. /// init activity state
  255. mAccountMgr = AccountManager.get(this);
  256. /// get input values
  257. mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE);
  258. Bundle extras = getIntent().getExtras();
  259. if (extras != null) {
  260. mAccount = extras.getParcelable(EXTRA_ACCOUNT);
  261. }
  262. if (savedInstanceState != null) {
  263. mWaitingForOpId = savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID);
  264. mIsFirstAuthAttempt = savedInstanceState.getBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG);
  265. }
  266. String webloginUrl = null;
  267. boolean showLegacyLogin;
  268. if (getIntent().getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
  269. webViewLoginMethod = true;
  270. webloginUrl = getString(R.string.provider_registration_server);
  271. showLegacyLogin = false;
  272. } else {
  273. webViewLoginMethod = !TextUtils.isEmpty(getResources().getString(R.string.webview_login_url));
  274. showLegacyLogin = true;
  275. }
  276. if (webViewLoginMethod) {
  277. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
  278. }
  279. /// load user interface
  280. if (!webViewLoginMethod) {
  281. setContentView(R.layout.account_setup);
  282. /// initialize general UI elements
  283. initOverallUi();
  284. findViewById(R.id.centeredRefreshButton).setOnClickListener(v -> checkOcServer());
  285. findViewById(R.id.embeddedRefreshButton).setOnClickListener(v -> checkOcServer());
  286. /// initialize block to be moved to single Fragment to check server and get info about it
  287. /// initialize block to be moved to single Fragment to retrieve and validate credentials
  288. initAuthorizationPreFragment(savedInstanceState);
  289. } else {
  290. setContentView(R.layout.account_setup_webview);
  291. mLoginWebView = findViewById(R.id.login_webview);
  292. initWebViewLogin(webloginUrl, showLegacyLogin, false);
  293. }
  294. initServerPreFragment(savedInstanceState);
  295. }
  296. private void deleteCookies() {
  297. try {
  298. CookieSyncManager.createInstance(this);
  299. CookieManager cookieManager = CookieManager.getInstance();
  300. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  301. cookieManager.removeAllCookies(null);
  302. } else {
  303. cookieManager.removeAllCookie();
  304. }
  305. } catch (AndroidRuntimeException e) {
  306. Log_OC.e(TAG, e.getMessage());
  307. }
  308. }
  309. private static String getWebLoginUserAgent() {
  310. return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
  311. Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL + " (Android)";
  312. }
  313. @SuppressLint("SetJavaScriptEnabled")
  314. private void initWebViewLogin(String baseURL, boolean showLegacyLogin, boolean useGenericUserAgent) {
  315. mLoginWebView.setVisibility(View.GONE);
  316. final ProgressBar progressBar = findViewById(R.id.login_webview_progress_bar);
  317. mLoginWebView.getSettings().setAllowFileAccess(false);
  318. mLoginWebView.getSettings().setJavaScriptEnabled(true);
  319. mLoginWebView.getSettings().setDomStorageEnabled(true);
  320. if (useGenericUserAgent) {
  321. mLoginWebView.getSettings().setUserAgentString(MainApp.getUserAgent());
  322. } else {
  323. mLoginWebView.getSettings().setUserAgentString(getWebLoginUserAgent());
  324. }
  325. mLoginWebView.getSettings().setSaveFormData(false);
  326. mLoginWebView.getSettings().setSavePassword(false);
  327. Map<String, String> headers = new HashMap<>();
  328. headers.put(RemoteOperation.OCS_API_HEADER, RemoteOperation.OCS_API_HEADER_VALUE);
  329. String url;
  330. if (baseURL != null && !baseURL.isEmpty()) {
  331. url = baseURL;
  332. } else {
  333. url = getResources().getString(R.string.webview_login_url);
  334. }
  335. mLoginWebView.loadUrl(url, headers);
  336. setClient(progressBar);
  337. // show snackbar after 60s to switch back to old login method
  338. if (showLegacyLogin) {
  339. final String finalBaseURL = baseURL;
  340. new Handler().postDelayed(() -> DisplayUtils.createSnackbar(mLoginWebView,
  341. R.string.fallback_weblogin_text,
  342. Snackbar.LENGTH_INDEFINITE)
  343. .setActionTextColor(getResources().getColor(R.color.white))
  344. .setAction(R.string.fallback_weblogin_back, v -> {
  345. mLoginWebView.setVisibility(View.INVISIBLE);
  346. webViewLoginMethod = false;
  347. setContentView(R.layout.account_setup);
  348. // initialize general UI elements
  349. initOverallUi();
  350. mPasswordInputLayout.setVisibility(View.VISIBLE);
  351. mUsernameInputLayout.setVisibility(View.VISIBLE);
  352. mUsernameInput.requestFocus();
  353. mAuthStatusView.setVisibility(View.INVISIBLE);
  354. mServerStatusView.setVisibility(View.INVISIBLE);
  355. mTestServerButton.setVisibility(View.INVISIBLE);
  356. forceOldLoginMethod = true;
  357. mOkButton.setVisibility(View.VISIBLE);
  358. initServerPreFragment(null);
  359. if (finalBaseURL != null) {
  360. mHostUrlInput.setText(finalBaseURL.replace(WEB_LOGIN, ""));
  361. } else {
  362. mHostUrlInput.setText(finalBaseURL);
  363. }
  364. checkOcServer();
  365. }).show(), 60 * 1000);
  366. }
  367. }
  368. @Override
  369. public boolean onKeyDown(int keyCode, KeyEvent event) {
  370. if (mLoginWebView != null && event.getAction() == KeyEvent.ACTION_DOWN) {
  371. switch (keyCode) {
  372. case KeyEvent.KEYCODE_BACK:
  373. if (mLoginWebView.canGoBack()) {
  374. mLoginWebView.goBack();
  375. } else {
  376. finish();
  377. }
  378. return true;
  379. }
  380. }
  381. return super.onKeyDown(keyCode, event);
  382. }
  383. private void setClient(ProgressBar progressBar) {
  384. mLoginWebView.setWebViewClient(new WebViewClient() {
  385. @Override
  386. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  387. if (url.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) {
  388. parseAndLoginFromWebView(url);
  389. return true;
  390. }
  391. return false;
  392. }
  393. @Override
  394. public void onPageFinished(WebView view, String url) {
  395. super.onPageFinished(view, url);
  396. progressBar.setVisibility(View.GONE);
  397. mLoginWebView.setVisibility(View.VISIBLE);
  398. }
  399. @Override
  400. public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
  401. X509Certificate cert = getX509CertificateFromError(error);
  402. try {
  403. if (cert != null && NetworkUtils.isCertInKnownServersStore(cert, getApplicationContext())) {
  404. handler.proceed();
  405. } else {
  406. showUntrustedCertDialog(cert, error, handler);
  407. }
  408. } catch (Exception e) {
  409. Log_OC.e(TAG, "Cert could not be verified");
  410. }
  411. }
  412. public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
  413. progressBar.setVisibility(View.GONE);
  414. mLoginWebView.setVisibility(View.VISIBLE);
  415. InputStream resources = getResources().openRawResource(R.raw.custom_error);
  416. String customError = DisplayUtils.getData(resources);
  417. if (!customError.isEmpty()) {
  418. mLoginWebView.loadData(customError, "text/html; charset=UTF-8", null);
  419. }
  420. }
  421. });
  422. }
  423. private void parseAndLoginFromWebView(String dataString) {
  424. String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
  425. LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString);
  426. if (loginUrlInfo != null) {
  427. try {
  428. mHostUrlInput.setText("");
  429. mServerInfo.mBaseUrl = AuthenticatorUrlUtils.normalizeUrlSuffix(loginUrlInfo.serverAddress);
  430. webViewUser = loginUrlInfo.username;
  431. webViewPassword = loginUrlInfo.password;
  432. } catch (Exception e) {
  433. mServerStatusIcon = R.drawable.ic_alert;
  434. mServerStatusText = "QR Code could not be read!";
  435. showServerStatus();
  436. }
  437. checkOcServer();
  438. }
  439. }
  440. private void populateLoginFields(String dataString) throws IllegalArgumentException {
  441. // check if it is cloud://login/
  442. if (dataString.startsWith(getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/")) {
  443. String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
  444. LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, dataString);
  445. if (loginUrlInfo != null) {
  446. mHostUrlInput.setText(loginUrlInfo.serverAddress);
  447. mUsernameInput.setText(loginUrlInfo.username);
  448. mPasswordInput.setText(loginUrlInfo.password);
  449. if (loginUrlInfo.serverAddress != null && !mServerIsChecked) {
  450. onUrlInputFocusLost();
  451. }
  452. }
  453. }
  454. }
  455. /**
  456. * parses a URI string and returns a login data object with the information from the URI string.
  457. *
  458. * @param prefix URI beginning, e.g. cloud://login/
  459. * @param dataString the complete URI
  460. * @return login data
  461. * @throws IllegalArgumentException when
  462. */
  463. public static LoginUrlInfo parseLoginDataUrl(String prefix, String dataString) throws IllegalArgumentException {
  464. if (dataString.length() < prefix.length()) {
  465. throw new IllegalArgumentException("Invalid login URL detected");
  466. }
  467. // format is basically xxx://login/server:xxx&user:xxx&password while all variables are optional
  468. String data = dataString.substring(prefix.length());
  469. // parse data
  470. String[] values = data.split("&");
  471. if (values.length < 1 || values.length > 3) {
  472. // error illegal number of URL elements detected
  473. throw new IllegalArgumentException("Illegal number of login URL elements detected: " + values.length);
  474. }
  475. LoginUrlInfo loginUrlInfo = new LoginUrlInfo();
  476. for (String value : values) {
  477. if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
  478. loginUrlInfo.username = URLDecoder.decode(
  479. value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
  480. } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
  481. loginUrlInfo.password = URLDecoder.decode(
  482. value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
  483. } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
  484. loginUrlInfo.serverAddress = URLDecoder.decode(
  485. value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
  486. }
  487. }
  488. return loginUrlInfo;
  489. }
  490. /**
  491. * Configures elements in the user interface under direct control of the Activity.
  492. */
  493. private void initOverallUi() {
  494. mHostUrlInput = findViewById(R.id.hostUrlInput);
  495. mUsernameInputLayout = findViewById(R.id.input_layout_account_username);
  496. mPasswordInputLayout = findViewById(R.id.input_layout_account_password);
  497. mPasswordInput = findViewById(R.id.account_password);
  498. mUsernameInput = findViewById(R.id.account_username);
  499. mAuthStatusView = findViewById(R.id.auth_status_text);
  500. mServerStatusView = findViewById(R.id.server_status_text);
  501. mTestServerButton = findViewById(R.id.testServerButton);
  502. mOkButton = findViewById(R.id.buttonOK);
  503. mOkButton.setOnClickListener(v -> onOkClick());
  504. ImageButton scanQR = findViewById(R.id.scanQR);
  505. if (deviceInfo.hasCamera(this)) {
  506. scanQR.setOnClickListener(v -> onScan());
  507. } else {
  508. scanQR.setVisibility(View.GONE);
  509. }
  510. setupInstructionMessage();
  511. mTestServerButton.setVisibility(mAction == ACTION_CREATE ? View.VISIBLE : View.GONE);
  512. }
  513. private void setupInstructionMessage() {
  514. TextView instructionsView = findViewById(R.id.instructions_message);
  515. if (mAction == ACTION_UPDATE_EXPIRED_TOKEN) {
  516. instructionsView.setVisibility(View.VISIBLE);
  517. String instructionsMessageText = getString(R.string.auth_expired_basic_auth_toast);
  518. instructionsView.setText(instructionsMessageText);
  519. } else {
  520. instructionsView.setVisibility(View.GONE);
  521. }
  522. }
  523. public void onTestServerConnectionClick(View v) {
  524. checkOcServer();
  525. }
  526. /**
  527. * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
  528. */
  529. private void initServerPreFragment(Bundle savedInstanceState) {
  530. /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
  531. boolean isUrlInputAllowed = getResources().getBoolean(R.bool.show_server_url_input);
  532. if (savedInstanceState == null) {
  533. if (mAccount != null) {
  534. mServerInfo.mBaseUrl = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_BASE_URL);
  535. // TODO do next in a setter for mBaseUrl
  536. mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith(HTTPS_PROTOCOL);
  537. mServerInfo.mVersion = accountManager.getServerVersion(mAccount);
  538. } else {
  539. if (!webViewLoginMethod) {
  540. mServerInfo.mBaseUrl = getString(R.string.server_url).trim();
  541. } else {
  542. mServerInfo.mBaseUrl = getString(R.string.webview_login_url).trim();
  543. }
  544. mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith(HTTPS_PROTOCOL);
  545. }
  546. } else {
  547. mServerStatusText = savedInstanceState.getString(KEY_SERVER_STATUS_TEXT);
  548. mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON);
  549. mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID);
  550. mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED);
  551. // TODO parcelable
  552. mServerInfo.mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN);
  553. mServerInfo.mBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT);
  554. String ocVersion = savedInstanceState.getString(KEY_OC_VERSION);
  555. if (ocVersion != null) {
  556. mServerInfo.mVersion = new OwnCloudVersion(ocVersion);
  557. }
  558. mServerInfo.mAuthMethod = AuthenticationMethod.valueOf(
  559. savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));
  560. }
  561. if (!webViewLoginMethod) {
  562. /// step 2 - set properties of UI elements (text, visibility, enabled...)
  563. mHostUrlInput = findViewById(R.id.hostUrlInput);
  564. // Convert IDN to Unicode
  565. mHostUrlInput.setText(DisplayUtils.convertIdn(mServerInfo.mBaseUrl, false));
  566. if (mAction != ACTION_CREATE) {
  567. /// lock things that should not change
  568. mHostUrlInput.setEnabled(false);
  569. mHostUrlInput.setFocusable(false);
  570. }
  571. if (isUrlInputAllowed) {
  572. mRefreshButton = findViewById(R.id.embeddedRefreshButton);
  573. } else {
  574. findViewById(R.id.hostUrlFrame).setVisibility(View.GONE);
  575. mRefreshButton = findViewById(R.id.centeredRefreshButton);
  576. }
  577. showRefreshButton(mServerIsChecked && !mServerIsValid &&
  578. mWaitingForOpId > Integer.MAX_VALUE);
  579. mServerStatusView = findViewById(R.id.server_status_text);
  580. showServerStatus();
  581. /// step 3 - bind some listeners and options
  582. mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
  583. mHostUrlInput.setOnEditorActionListener(this);
  584. /// step 4 - create listeners that will be bound at onResume
  585. mHostUrlInputWatcher = new TextWatcher() {
  586. @Override
  587. public void afterTextChanged(Editable s) {
  588. if (mOkButton.isEnabled() &&
  589. !mServerInfo.mBaseUrl.equals(
  590. AuthenticatorUrlUtils.normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) {
  591. mOkButton.setEnabled(false);
  592. }
  593. }
  594. @Override
  595. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  596. // not used at the moment
  597. }
  598. @Override
  599. public void onTextChanged(CharSequence s, int start, int before, int count) {
  600. if (mAuthStatusIcon != 0) {
  601. Log_OC.d(TAG, "onTextChanged: hiding authentication status");
  602. mAuthStatusIcon = 0;
  603. mAuthStatusText = EMPTY_STRING;
  604. showAuthStatus();
  605. }
  606. }
  607. };
  608. }
  609. }
  610. /**
  611. * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)}
  612. */
  613. private void initAuthorizationPreFragment(Bundle savedInstanceState) {
  614. /// step 0 - get UI elements in layout
  615. mUsernameInput = findViewById(R.id.account_username);
  616. mPasswordInput = findViewById(R.id.account_password);
  617. mAuthStatusView = findViewById(R.id.auth_status_text);
  618. /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState)
  619. String presetUserName = null;
  620. boolean isPasswordExposed = false;
  621. if (savedInstanceState == null) {
  622. if (mAccount != null) {
  623. presetUserName = com.owncloud.android.lib.common.accounts.AccountUtils.getUsernameForAccount(mAccount);
  624. }
  625. } else {
  626. isPasswordExposed = savedInstanceState.getBoolean(KEY_PASSWORD_EXPOSED, false);
  627. mAuthStatusText = savedInstanceState.getString(KEY_AUTH_STATUS_TEXT);
  628. mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON);
  629. mAuthToken = savedInstanceState.getString(KEY_AUTH_TOKEN);
  630. }
  631. /// step 2 - set properties of UI elements (text, visibility, enabled...)
  632. if (presetUserName != null) {
  633. mUsernameInput.setText(presetUserName);
  634. }
  635. if (mAction != ACTION_CREATE) {
  636. mUsernameInput.setEnabled(false);
  637. mUsernameInput.setFocusable(false);
  638. }
  639. mPasswordInput.setText(EMPTY_STRING); // clean password to avoid social hacking
  640. if (isPasswordExposed) {
  641. showPassword();
  642. }
  643. showAuthStatus();
  644. mOkButton.setEnabled(mServerIsValid);
  645. /// step 3 - bind listeners
  646. // bindings for password input field
  647. mPasswordInput.setOnFocusChangeListener(this);
  648. mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
  649. mPasswordInput.setOnEditorActionListener(this);
  650. mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() {
  651. @Override
  652. public boolean onDrawableTouch(final MotionEvent event) {
  653. if (event.getAction() == MotionEvent.ACTION_UP) {
  654. AuthenticatorActivity.this.onViewPasswordClick();
  655. }
  656. return true;
  657. }
  658. });
  659. }
  660. /**
  661. * Saves relevant state before {@link #onPause()}
  662. *
  663. * See {@link super#onSaveInstanceState(Bundle)}
  664. */
  665. @Override
  666. protected void onSaveInstanceState(Bundle outState) {
  667. //Log_OC.e(TAG, "onSaveInstanceState init" );
  668. super.onSaveInstanceState(outState);
  669. /// global state
  670. outState.putLong(KEY_WAITING_FOR_OP_ID, mWaitingForOpId);
  671. if (!webViewLoginMethod) {
  672. /// Server PRE-fragment state
  673. outState.putString(KEY_SERVER_STATUS_TEXT, mServerStatusText);
  674. outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon);
  675. outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked);
  676. outState.putBoolean(KEY_SERVER_VALID, mServerIsValid);
  677. /// Authentication PRE-fragment state
  678. outState.putBoolean(KEY_PASSWORD_EXPOSED, isPasswordVisible());
  679. outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon);
  680. outState.putString(KEY_AUTH_STATUS_TEXT, mAuthStatusText);
  681. outState.putString(KEY_AUTH_TOKEN, mAuthToken);
  682. }
  683. outState.putBoolean(KEY_IS_SSL_CONN, mServerInfo.mIsSslConn);
  684. outState.putString(KEY_HOST_URL_TEXT, mServerInfo.mBaseUrl);
  685. if (mServerInfo.mVersion != null) {
  686. outState.putString(KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
  687. }
  688. outState.putString(KEY_SERVER_AUTH_METHOD, mServerInfo.mAuthMethod.name());
  689. /// authentication
  690. outState.putBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG, mIsFirstAuthAttempt);
  691. /// AsyncTask (User and password)
  692. if (!webViewLoginMethod) {
  693. outState.putString(KEY_USERNAME, mUsernameInput.getText().toString().trim());
  694. outState.putString(KEY_PASSWORD, mPasswordInput.getText().toString());
  695. }
  696. if (mAsyncTask != null) {
  697. mAsyncTask.cancel(true);
  698. outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, true);
  699. } else {
  700. outState.putBoolean(KEY_ASYNC_TASK_IN_PROGRESS, false);
  701. }
  702. mAsyncTask = null;
  703. //Log_OC.e(TAG, "onSaveInstanceState end" );
  704. }
  705. @Override
  706. public void onRestoreInstanceState(Bundle savedInstanceState) {
  707. super.onRestoreInstanceState(savedInstanceState);
  708. mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED, false);
  709. // AsyncTask
  710. boolean inProgress = savedInstanceState.getBoolean(KEY_ASYNC_TASK_IN_PROGRESS);
  711. if (inProgress) {
  712. String username = savedInstanceState.getString(KEY_USERNAME);
  713. String password = savedInstanceState.getString(KEY_PASSWORD);
  714. OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
  715. accessRootFolder(credentials);
  716. }
  717. }
  718. /**
  719. * The redirection triggered by the OAuth authentication server as response to the
  720. * GET AUTHORIZATION request is caught here.
  721. *
  722. * To make this possible, this activity needs to be qualified with android:launchMode =
  723. * "singleTask" in the AndroidManifest.xml file.
  724. */
  725. @Override
  726. protected void onNewIntent(Intent intent) {
  727. Log_OC.d(TAG, "onNewIntent()");
  728. if (intent.getBooleanExtra(FirstRunActivity.EXTRA_EXIT, false)) {
  729. super.finish();
  730. }
  731. // Passcode
  732. PassCodeManager passCodeManager = new PassCodeManager(preferences);
  733. passCodeManager.onActivityStarted(this);
  734. Uri data = intent.getData();
  735. if (data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme))) {
  736. parseAndLoginFromWebView(data.toString());
  737. }
  738. if (intent.getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
  739. webViewLoginMethod = true;
  740. setContentView(R.layout.account_setup_webview);
  741. mLoginWebView = findViewById(R.id.login_webview);
  742. initWebViewLogin(getString(R.string.provider_registration_server), false, true);
  743. }
  744. }
  745. /**
  746. * The redirection triggered by the OAuth authentication server as response to the
  747. * GET AUTHORIZATION, and deferred in {@link #onNewIntent(Intent)}, is processed here.
  748. */
  749. @Override
  750. protected void onResume() {
  751. super.onResume();
  752. if (!webViewLoginMethod) {
  753. // bound here to avoid spurious changes triggered by Android on device rotations
  754. mHostUrlInput.setOnFocusChangeListener(this);
  755. mHostUrlInput.addTextChangedListener(mHostUrlInputWatcher);
  756. String dataString = getIntent().getDataString();
  757. if (dataString != null) {
  758. try {
  759. populateLoginFields(dataString);
  760. } catch (IllegalArgumentException e) {
  761. DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_illegal_login_used);
  762. Log_OC.e(TAG, "Illegal login data URL used, no Login pre-fill!", e);
  763. }
  764. }
  765. }
  766. // bind to Operations Service
  767. mOperationsServiceConnection = new OperationsServiceConnection();
  768. if (!bindService(new Intent(this, OperationsService.class),
  769. mOperationsServiceConnection,
  770. Context.BIND_AUTO_CREATE)) {
  771. DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.error_cant_bind_to_operations_service);
  772. finish();
  773. }
  774. if (mOperationsServiceBinder != null) {
  775. doOnResumeAndBound();
  776. }
  777. }
  778. @Override
  779. protected void onPause() {
  780. if (mOperationsServiceBinder != null) {
  781. mOperationsServiceBinder.removeOperationListener(this);
  782. }
  783. if (!webViewLoginMethod) {
  784. mHostUrlInput.removeTextChangedListener(mHostUrlInputWatcher);
  785. mHostUrlInput.setOnFocusChangeListener(null);
  786. }
  787. super.onPause();
  788. }
  789. @Override
  790. protected void onDestroy() {
  791. mHostUrlInputWatcher = null;
  792. if (mOperationsServiceConnection != null) {
  793. unbindService(mOperationsServiceConnection);
  794. mOperationsServiceBinder = null;
  795. }
  796. if (webViewLoginMethod) {
  797. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
  798. }
  799. super.onDestroy();
  800. }
  801. /**
  802. * Handles the change of focus on the text inputs for the server URL and the password
  803. */
  804. public void onFocusChange(View view, boolean hasFocus) {
  805. if (view.getId() == R.id.hostUrlInput) {
  806. if (!hasFocus) {
  807. onUrlInputFocusLost();
  808. } else {
  809. showRefreshButton(false);
  810. }
  811. } else if (view.getId() == R.id.account_password) {
  812. onPasswordFocusChanged(hasFocus);
  813. }
  814. }
  815. /**
  816. * Handles changes in focus on the text input for the server URL.
  817. *
  818. * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to
  819. * other field. The operation to check the existence of the server in the entered URL is
  820. * started.
  821. *
  822. * When hasFocus: user 'comes back' to write again the server URL.
  823. */
  824. private void onUrlInputFocusLost() {
  825. if (!mServerInfo.mBaseUrl.equals(
  826. AuthenticatorUrlUtils.normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) {
  827. // check server again only if the user changed something in the field
  828. checkOcServer();
  829. } else {
  830. mOkButton.setEnabled(mServerIsValid);
  831. showRefreshButton(!mServerIsValid);
  832. }
  833. }
  834. private void checkOcServer() {
  835. String uri;
  836. if (mHostUrlInput != null && !mHostUrlInput.getText().toString().isEmpty()) {
  837. uri = mHostUrlInput.getText().toString().trim();
  838. mOkButton.setEnabled(false);
  839. showRefreshButton(false);
  840. } else {
  841. uri = mServerInfo.mBaseUrl;
  842. }
  843. mServerIsValid = false;
  844. mServerIsChecked = false;
  845. mServerInfo = new GetServerInfoOperation.ServerInfo();
  846. if (uri.length() != 0) {
  847. if (mHostUrlInput != null) {
  848. uri = AuthenticatorUrlUtils.stripIndexPhpOrAppsFiles(uri);
  849. mHostUrlInput.setText(uri);
  850. }
  851. // Handle internationalized domain names
  852. try {
  853. uri = DisplayUtils.convertIdn(uri, true);
  854. } catch (IllegalArgumentException ex) {
  855. // Let Owncloud library check the error of the malformed URI
  856. Log_OC.e(TAG, "Error converting internationalized domain name " + uri, ex);
  857. }
  858. if (mHostUrlInput != null) {
  859. mServerStatusText = getResources().getString(R.string.auth_testing_connection);
  860. mServerStatusIcon = R.drawable.progress_small;
  861. showServerStatus();
  862. }
  863. Intent getServerInfoIntent = new Intent();
  864. getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO);
  865. getServerInfoIntent.putExtra(OperationsService.EXTRA_SERVER_URL,
  866. AuthenticatorUrlUtils.normalizeUrlSuffix(uri));
  867. if (mOperationsServiceBinder != null) {
  868. mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
  869. } else {
  870. Log_OC.e(TAG, "Server check tried with OperationService unbound!");
  871. }
  872. } else {
  873. mServerStatusText = EMPTY_STRING;
  874. mServerStatusIcon = 0;
  875. if (!webViewLoginMethod) {
  876. showServerStatus();
  877. }
  878. }
  879. }
  880. /**
  881. * Handles changes in focus on the text input for the password (basic authorization).
  882. *
  883. * When (hasFocus), the button to toggle password visibility is shown.
  884. *
  885. * When (!hasFocus), the button is made invisible and the password is hidden.
  886. *
  887. * @param hasFocus 'True' if focus is received, 'false' if is lost
  888. */
  889. private void onPasswordFocusChanged(boolean hasFocus) {
  890. if (hasFocus) {
  891. showViewPasswordButton();
  892. } else {
  893. hidePassword();
  894. hidePasswordButton();
  895. }
  896. }
  897. private void showViewPasswordButton() {
  898. int drawable = R.drawable.ic_view;
  899. if (isPasswordVisible()) {
  900. drawable = R.drawable.ic_hide;
  901. }
  902. mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0);
  903. }
  904. private boolean isPasswordVisible() {
  905. return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) ==
  906. InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
  907. }
  908. private void hidePasswordButton() {
  909. mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
  910. }
  911. private void showPassword() {
  912. mPasswordInput.setInputType(
  913. InputType.TYPE_CLASS_TEXT |
  914. InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD |
  915. InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
  916. );
  917. showViewPasswordButton();
  918. }
  919. private void hidePassword() {
  920. mPasswordInput.setInputType(
  921. InputType.TYPE_CLASS_TEXT |
  922. InputType.TYPE_TEXT_VARIATION_PASSWORD |
  923. InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
  924. );
  925. showViewPasswordButton();
  926. }
  927. /**
  928. * Checks the credentials of the user in the root of the ownCloud server
  929. * before creating a new local account.
  930. *
  931. * For basic authorization, a check of existence of the root folder is
  932. * performed.
  933. *
  934. * For OAuth, starts the flow to get an access token; the credentials test
  935. * is postponed until it is available.
  936. *
  937. * IMPORTANT ENTRY POINT 4
  938. */
  939. public void onOkClick() {
  940. // this check should be unnecessary
  941. if (mServerInfo.mVersion == null ||
  942. !mServerInfo.mVersion.isVersionValid() ||
  943. TextUtils.isEmpty(mServerInfo.mBaseUrl)) {
  944. mServerStatusIcon = R.drawable.ic_alert;
  945. mServerStatusText = getResources().getString(R.string.auth_wtf_reenter_URL);
  946. showServerStatus();
  947. mOkButton.setEnabled(false);
  948. return;
  949. }
  950. checkBasicAuthorization(null, null);
  951. }
  952. /**
  953. * Tests the credentials entered by the user performing a check of existence on
  954. * the root folder of the ownCloud server.
  955. */
  956. private void checkBasicAuthorization(@Nullable String webViewUsername, @Nullable String webViewPassword) {
  957. /// get basic credentials entered by user
  958. String username;
  959. String password;
  960. if (!webViewLoginMethod) {
  961. username = mUsernameInput.getText().toString().trim();
  962. password = mPasswordInput.getText().toString();
  963. } else {
  964. username = webViewUsername;
  965. password = webViewPassword;
  966. }
  967. /// be gentle with the user
  968. IndeterminateProgressDialog dialog = IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login,
  969. true);
  970. FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
  971. ft.add(dialog, WAIT_DIALOG_TAG);
  972. ft.commitAllowingStateLoss();
  973. /// validate credentials accessing the root folder
  974. OwnCloudCredentials credentials = OwnCloudCredentialsFactory.newBasicCredentials(username, password);
  975. accessRootFolder(credentials);
  976. }
  977. private void accessRootFolder(OwnCloudCredentials credentials) {
  978. mAsyncTask = new AuthenticatorAsyncTask(this);
  979. Object[] params = {mServerInfo.mBaseUrl, credentials};
  980. mAsyncTask.execute(params);
  981. }
  982. /**
  983. * Callback method invoked when a RemoteOperation executed by this Activity finishes.
  984. *
  985. * Dispatches the operation flow to the right method.
  986. */
  987. @Override
  988. public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
  989. if (operation instanceof GetServerInfoOperation) {
  990. if (operation.hashCode() == mWaitingForOpId) {
  991. onGetServerInfoFinish(result);
  992. } // else nothing ; only the last check operation is considered;
  993. // multiple can be started if the user amends a URL quickly
  994. } else if (operation instanceof GetUserInfoRemoteOperation) {
  995. onGetUserNameFinish(result);
  996. }
  997. }
  998. private void onGetUserNameFinish(RemoteOperationResult result) {
  999. mWaitingForOpId = Long.MAX_VALUE;
  1000. if (result.isSuccess()) {
  1001. boolean success = false;
  1002. String username;
  1003. if (result.getData().get(0) instanceof UserInfo) {
  1004. username = ((UserInfo) result.getData().get(0)).getDisplayName();
  1005. } else {
  1006. username = (String) result.getData().get(0);
  1007. }
  1008. if (mAction == ACTION_CREATE) {
  1009. if (!webViewLoginMethod) {
  1010. mUsernameInput.setText(username);
  1011. }
  1012. success = createAccount(result);
  1013. } else {
  1014. if (!webViewLoginMethod && !mUsernameInput.getText().toString().trim().equals(username)) {
  1015. // fail - not a new account, but an existing one; disallow
  1016. result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME);
  1017. mAuthToken = EMPTY_STRING;
  1018. updateAuthStatusIconAndText(result);
  1019. showAuthStatus();
  1020. Log_OC.d(TAG, result.getLogMessage());
  1021. } else {
  1022. try {
  1023. updateAccountAuthentication();
  1024. success = true;
  1025. } catch (AccountNotFoundException e) {
  1026. Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
  1027. DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_account_does_not_exist);
  1028. finish();
  1029. }
  1030. }
  1031. }
  1032. if (success) {
  1033. finish();
  1034. }
  1035. } else {
  1036. if (!webViewLoginMethod) {
  1037. int statusText = result.getCode() == ResultCode.MAINTENANCE_MODE ? R.string.maintenance_mode : R.string.auth_fail_get_user_name;
  1038. updateStatusIconFailUserName(statusText);
  1039. showAuthStatus();
  1040. }
  1041. Log_OC.e(TAG, "Access to user name failed: " + result.getLogMessage());
  1042. }
  1043. }
  1044. /**
  1045. * Processes the result of the server check performed when the user finishes the enter of the
  1046. * server URL.
  1047. *
  1048. * @param result Result of the check.
  1049. */
  1050. private void onGetServerInfoFinish(RemoteOperationResult result) {
  1051. /// update activity state
  1052. mServerIsChecked = true;
  1053. mWaitingForOpId = Long.MAX_VALUE;
  1054. // update server status, but don't show it yet
  1055. if (!webViewLoginMethod) {
  1056. updateServerStatusIconAndText(result);
  1057. }
  1058. if (result.isSuccess()) {
  1059. /// SUCCESS means:
  1060. // 1. connection succeeded, and we know if it's SSL or not
  1061. // 2. server is installed
  1062. // 3. we got the server version
  1063. // 4. we got the authentication method required by the server
  1064. mServerInfo = (GetServerInfoOperation.ServerInfo) (result.getData().get(0));
  1065. // show outdated warning
  1066. if (getResources().getBoolean(R.bool.show_outdated_server_warning) &&
  1067. MainApp.OUTDATED_SERVER_VERSION.isSameMajorVersion(mServerInfo.mVersion) &&
  1068. !mServerInfo.hasExtendedSupport) {
  1069. DisplayUtils.showServerOutdatedSnackbar(this, Snackbar.LENGTH_INDEFINITE);
  1070. }
  1071. webViewLoginMethod = mServerInfo.mVersion.isWebLoginSupported() && !forceOldLoginMethod;
  1072. if (webViewUser != null && !webViewUser.isEmpty() &&
  1073. webViewPassword != null && !webViewPassword.isEmpty()) {
  1074. checkBasicAuthorization(webViewUser, webViewPassword);
  1075. } else if (webViewLoginMethod) {
  1076. // hide old login
  1077. setOldLoginVisibility(View.GONE);
  1078. setContentView(R.layout.account_setup_webview);
  1079. mLoginWebView = findViewById(R.id.login_webview);
  1080. initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, true, false);
  1081. } else {
  1082. // show old login
  1083. setOldLoginVisibility(View.VISIBLE);
  1084. }
  1085. if (!authSupported(mServerInfo.mAuthMethod)) {
  1086. if (!webViewLoginMethod) {
  1087. // overrides updateServerStatusIconAndText()
  1088. updateServerStatusIconNoRegularAuth();
  1089. }
  1090. mServerIsValid = false;
  1091. } else {
  1092. mServerIsValid = true;
  1093. }
  1094. } else {
  1095. mServerIsValid = false;
  1096. }
  1097. // refresh UI
  1098. if (!webViewLoginMethod) {
  1099. showRefreshButton(!mServerIsValid);
  1100. showServerStatus();
  1101. mOkButton.setEnabled(mServerIsValid);
  1102. }
  1103. if (!mServerIsValid) {
  1104. // hide old login
  1105. setOldLoginVisibility(View.GONE);
  1106. }
  1107. /// very special case (TODO: move to a common place for all the remote operations)
  1108. if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
  1109. showUntrustedCertDialog(result);
  1110. }
  1111. }
  1112. private void setOldLoginVisibility(int visible) {
  1113. mOkButton.setVisibility(visible);
  1114. mUsernameInputLayout.setVisibility(visible);
  1115. mPasswordInputLayout.setVisibility(visible);
  1116. }
  1117. private boolean authSupported(AuthenticationMethod authMethod) {
  1118. return AuthenticationMethod.BASIC_HTTP_AUTH.equals(authMethod);
  1119. }
  1120. /**
  1121. * Chooses the right icon and text to show to the user for the received operation result.
  1122. *
  1123. * @param result Result of a remote operation performed in this activity
  1124. */
  1125. private void updateServerStatusIconAndText(RemoteOperationResult result) {
  1126. mServerStatusIcon = R.drawable.ic_alert; // the most common case in the switch below
  1127. switch (result.getCode()) {
  1128. case OK_SSL:
  1129. mServerStatusIcon = R.drawable.ic_lock_white;
  1130. mServerStatusText = getResources().getString(R.string.auth_secure_connection);
  1131. break;
  1132. case OK_NO_SSL:
  1133. case OK:
  1134. if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
  1135. mServerStatusText = getResources().getString(R.string.auth_connection_established);
  1136. mServerStatusIcon = R.drawable.ic_ok;
  1137. } else {
  1138. mServerStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
  1139. mServerStatusIcon = R.drawable.ic_lock_open_white;
  1140. }
  1141. break;
  1142. case NO_NETWORK_CONNECTION:
  1143. mServerStatusIcon = R.drawable.no_network;
  1144. mServerStatusText = getResources().getString(R.string.auth_no_net_conn_title);
  1145. break;
  1146. case SSL_RECOVERABLE_PEER_UNVERIFIED:
  1147. mServerStatusText = getResources().getString(R.string.auth_ssl_unverified_server_title);
  1148. break;
  1149. case BAD_OC_VERSION:
  1150. mServerStatusText = getResources().getString(R.string.auth_bad_oc_version_title);
  1151. break;
  1152. case WRONG_CONNECTION:
  1153. mServerStatusText = getResources().getString(R.string.auth_wrong_connection_title);
  1154. break;
  1155. case TIMEOUT:
  1156. mServerStatusText = getResources().getString(R.string.auth_timeout_title);
  1157. break;
  1158. case INCORRECT_ADDRESS:
  1159. mServerStatusText = getResources().getString(R.string.auth_incorrect_address_title);
  1160. break;
  1161. case SSL_ERROR:
  1162. mServerStatusText = getResources().getString(R.string.auth_ssl_general_error_title);
  1163. break;
  1164. case UNAUTHORIZED:
  1165. mServerStatusText = getResources().getString(R.string.auth_unauthorized);
  1166. break;
  1167. case HOST_NOT_AVAILABLE:
  1168. mServerStatusText = getResources().getString(R.string.auth_unknown_host_title);
  1169. break;
  1170. case INSTANCE_NOT_CONFIGURED:
  1171. mServerStatusText = getResources().getString(R.string.auth_not_configured_title);
  1172. break;
  1173. case FILE_NOT_FOUND:
  1174. mServerStatusText = getResources().getString(R.string.auth_incorrect_path_title);
  1175. break;
  1176. case OAUTH2_ERROR:
  1177. mServerStatusText = getResources().getString(R.string.auth_oauth_error);
  1178. break;
  1179. case OAUTH2_ERROR_ACCESS_DENIED:
  1180. mServerStatusText = getResources().getString(R.string.auth_oauth_error_access_denied);
  1181. break;
  1182. case UNHANDLED_HTTP_CODE:
  1183. mServerStatusText = getResources().getString(R.string.auth_unknown_error_http_title);
  1184. break;
  1185. case UNKNOWN_ERROR:
  1186. if (result.getException() != null &&
  1187. !TextUtils.isEmpty(result.getException().getMessage())) {
  1188. mServerStatusText = getResources().getString(
  1189. R.string.auth_unknown_error_exception_title,
  1190. result.getException().getMessage()
  1191. );
  1192. } else {
  1193. mServerStatusText = getResources().getString(R.string.auth_unknown_error_title);
  1194. }
  1195. break;
  1196. case OK_REDIRECT_TO_NON_SECURE_CONNECTION:
  1197. mServerStatusIcon = R.drawable.ic_lock_open_white;
  1198. mServerStatusText = getResources().getString(R.string.auth_redirect_non_secure_connection_title);
  1199. break;
  1200. case MAINTENANCE_MODE:
  1201. mServerStatusText = getResources().getString(R.string.maintenance_mode);
  1202. break;
  1203. case UNTRUSTED_DOMAIN:
  1204. mServerStatusText = getResources().getString(R.string.untrusted_domain);
  1205. break;
  1206. default:
  1207. mServerStatusText = EMPTY_STRING;
  1208. mServerStatusIcon = 0;
  1209. break;
  1210. }
  1211. }
  1212. /**
  1213. * Chooses the right icon and text to show to the user for the received operation result.
  1214. *
  1215. * @param result Result of a remote operation performed in this activity
  1216. */
  1217. private void updateAuthStatusIconAndText(RemoteOperationResult result) {
  1218. mAuthStatusIcon = R.drawable.ic_alert; // the most common case in the switch below
  1219. switch (result.getCode()) {
  1220. case OK_SSL:
  1221. mAuthStatusIcon = R.drawable.ic_lock_white;
  1222. mAuthStatusText = getResources().getString(R.string.auth_secure_connection);
  1223. break;
  1224. case OK_NO_SSL:
  1225. case OK:
  1226. if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
  1227. mAuthStatusText = getResources().getString(R.string.auth_connection_established);
  1228. mAuthStatusIcon = R.drawable.ic_ok;
  1229. } else {
  1230. mAuthStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
  1231. mAuthStatusIcon = R.drawable.ic_lock_open_white;
  1232. }
  1233. break;
  1234. case NO_NETWORK_CONNECTION:
  1235. mAuthStatusIcon = R.drawable.no_network;
  1236. mAuthStatusText = getResources().getString(R.string.auth_no_net_conn_title);
  1237. break;
  1238. case SSL_RECOVERABLE_PEER_UNVERIFIED:
  1239. mAuthStatusText = getResources().getString(R.string.auth_ssl_unverified_server_title);
  1240. break;
  1241. case TIMEOUT:
  1242. mAuthStatusText = getResources().getString(R.string.auth_timeout_title);
  1243. break;
  1244. case HOST_NOT_AVAILABLE:
  1245. mAuthStatusText = getResources().getString(R.string.auth_unknown_host_title);
  1246. break;
  1247. case UNHANDLED_HTTP_CODE:
  1248. default:
  1249. mAuthStatusText = ErrorMessageAdapter.getErrorCauseMessage(result, null, getResources());
  1250. }
  1251. }
  1252. private void updateStatusIconFailUserName(int failedStatusText) {
  1253. mAuthStatusIcon = R.drawable.ic_alert;
  1254. mAuthStatusText = getResources().getString(failedStatusText);
  1255. }
  1256. private void updateServerStatusIconNoRegularAuth() {
  1257. mServerStatusIcon = R.drawable.ic_alert;
  1258. mServerStatusText = getResources().getString(R.string.auth_can_not_auth_against_server);
  1259. }
  1260. /**
  1261. * Processes the result of the access check performed to try the user credentials.
  1262. *
  1263. * Creates a new account through the AccountManager.
  1264. *
  1265. * @param result Result of the operation.
  1266. */
  1267. @Override
  1268. public void onAuthenticatorTaskCallback(RemoteOperationResult result) {
  1269. mWaitingForOpId = Long.MAX_VALUE;
  1270. dismissDialog(WAIT_DIALOG_TAG);
  1271. mAsyncTask = null;
  1272. if (result.isSuccess()) {
  1273. Log_OC.d(TAG, "Successful access - time to save the account");
  1274. boolean success = false;
  1275. if (mAction == ACTION_CREATE) {
  1276. success = createAccount(result);
  1277. } else {
  1278. try {
  1279. updateAccountAuthentication();
  1280. success = true;
  1281. } catch (AccountNotFoundException e) {
  1282. Log_OC.e(TAG, "Account " + mAccount + " was removed!", e);
  1283. DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.auth_account_does_not_exist);
  1284. finish();
  1285. }
  1286. }
  1287. // Reset webView
  1288. webViewPassword = null;
  1289. webViewUser = null;
  1290. forceOldLoginMethod = false;
  1291. deleteCookies();
  1292. if (success) {
  1293. finish();
  1294. accountManager.setCurrentOwnCloudAccount(mAccount.name);
  1295. Intent i = new Intent(this, FileDisplayActivity.class);
  1296. i.setAction(FileDisplayActivity.RESTART);
  1297. i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  1298. startActivity(i);
  1299. } else {
  1300. // init webView again
  1301. if (mLoginWebView != null) {
  1302. mLoginWebView.setVisibility(View.GONE);
  1303. }
  1304. setContentView(R.layout.account_setup);
  1305. initOverallUi();
  1306. CustomEditText serverAddressField = findViewById(R.id.hostUrlInput);
  1307. serverAddressField.setText(mServerInfo.mBaseUrl);
  1308. findViewById(R.id.server_status_text).setVisibility(View.GONE);
  1309. mAuthStatusView = findViewById(R.id.auth_status_text);
  1310. showAuthStatus();
  1311. }
  1312. } else if (result.isServerFail() || result.isException()) {
  1313. /// server errors or exceptions in authorization take to requiring a new check of
  1314. /// the server
  1315. mServerIsChecked = true;
  1316. mServerIsValid = false;
  1317. mServerInfo = new GetServerInfoOperation.ServerInfo();
  1318. // update status icon and text
  1319. updateServerStatusIconAndText(result);
  1320. showServerStatus();
  1321. mAuthStatusIcon = 0;
  1322. mAuthStatusText = EMPTY_STRING;
  1323. if (!webViewLoginMethod) {
  1324. showAuthStatus();
  1325. // update input controls state
  1326. showRefreshButton(true);
  1327. mOkButton.setEnabled(false);
  1328. }
  1329. // very special case (TODO: move to a common place for all the remote operations)
  1330. if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {
  1331. showUntrustedCertDialog(result);
  1332. }
  1333. } else { // authorization fail due to client side - probably wrong credentials
  1334. if (webViewLoginMethod) {
  1335. mLoginWebView = findViewById(R.id.login_webview);
  1336. if (mLoginWebView != null) {
  1337. initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, true, false);
  1338. DisplayUtils.showSnackMessage(this, mLoginWebView, R.string.auth_access_failed,
  1339. result.getLogMessage());
  1340. } else {
  1341. DisplayUtils.showSnackMessage(this, R.string.auth_access_failed, result.getLogMessage());
  1342. // init webView again
  1343. if (mLoginWebView != null) {
  1344. mLoginWebView.setVisibility(View.GONE);
  1345. }
  1346. setContentView(R.layout.account_setup);
  1347. initOverallUi();
  1348. CustomEditText serverAddressField = findViewById(R.id.hostUrlInput);
  1349. serverAddressField.setText(mServerInfo.mBaseUrl);
  1350. findViewById(R.id.server_status_text).setVisibility(View.GONE);
  1351. mAuthStatusView = findViewById(R.id.auth_status_text);
  1352. showAuthStatus();
  1353. }
  1354. } else {
  1355. updateAuthStatusIconAndText(result);
  1356. showAuthStatus();
  1357. }
  1358. // reset webview
  1359. webViewPassword = null;
  1360. webViewUser = null;
  1361. deleteCookies();
  1362. Log_OC.d(TAG, "Access failed: " + result.getLogMessage());
  1363. }
  1364. }
  1365. /**
  1366. * Updates the authentication token.
  1367. *
  1368. * Sets the proper response so that the AccountAuthenticator that started this activity
  1369. * saves a new authorization token for mAccount.
  1370. *
  1371. * Kills the session kept by OwnCloudClientManager so that a new one will created with
  1372. * the new credentials when needed.
  1373. */
  1374. private void updateAccountAuthentication() throws AccountNotFoundException {
  1375. Bundle response = new Bundle();
  1376. response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
  1377. response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
  1378. if (webViewLoginMethod) {
  1379. response.putString(AccountManager.KEY_AUTHTOKEN, webViewPassword);
  1380. mAccountMgr.setPassword(mAccount, webViewPassword);
  1381. } else {
  1382. response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
  1383. mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
  1384. }
  1385. // remove managed clients for this account to enforce creation with fresh credentials
  1386. OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, this);
  1387. OwnCloudClientManagerFactory.getDefaultSingleton().removeClientFor(ocAccount);
  1388. setAccountAuthenticatorResult(response);
  1389. final Intent intent = new Intent();
  1390. intent.putExtras(response);
  1391. setResult(RESULT_OK, intent);
  1392. }
  1393. /**
  1394. * Creates a new account through the Account Authenticator that started this activity.
  1395. *
  1396. * This makes the account permanent.
  1397. *
  1398. * TODO Decide how to name the OAuth accounts
  1399. */
  1400. @SuppressFBWarnings("DMI")
  1401. @SuppressLint("TrulyRandom")
  1402. protected boolean createAccount(RemoteOperationResult authResult) {
  1403. String accountType = MainApp.getAccountType(this);
  1404. // create and save new ownCloud account
  1405. String lastPermanentLocation = authResult.getLastPermanentLocation();
  1406. if (lastPermanentLocation != null) {
  1407. mServerInfo.mBaseUrl = AuthenticatorUrlUtils.trimWebdavSuffix(lastPermanentLocation);
  1408. }
  1409. Uri uri = Uri.parse(mServerInfo.mBaseUrl);
  1410. // used for authenticate on every login/network connection, determined by first login (weblogin/old login)
  1411. // can be anything: email, name, name with whitespaces
  1412. String loginName;
  1413. if (!webViewLoginMethod) {
  1414. loginName = mUsernameInput.getText().toString().trim();
  1415. } else {
  1416. loginName = webViewUser;
  1417. }
  1418. String accountName = com.owncloud.android.lib.common.accounts.AccountUtils.buildAccountName(uri, loginName);
  1419. Account newAccount = new Account(accountName, accountType);
  1420. if (accountManager.exists(newAccount)) {
  1421. // fail - not a new account, but an existing one; disallow
  1422. RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW);
  1423. updateAuthStatusIconAndText(result);
  1424. showAuthStatus();
  1425. Log_OC.d(TAG, result.getLogMessage());
  1426. return false;
  1427. } else {
  1428. mAccount = newAccount;
  1429. if (webViewLoginMethod) {
  1430. mAccountMgr.addAccountExplicitly(mAccount, webViewPassword, null);
  1431. } else {
  1432. mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null);
  1433. }
  1434. /// add the new account as default in preferences, if there is none already
  1435. Account defaultAccount = accountManager.getCurrentAccount();
  1436. if (defaultAccount == null) {
  1437. SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
  1438. editor.putString("select_oc_account", accountName);
  1439. editor.apply();
  1440. }
  1441. /// prepare result to return to the Authenticator
  1442. // TODO check again what the Authenticator makes with it; probably has the same
  1443. // effect as addAccountExplicitly, but it's not well done
  1444. final Intent intent = new Intent();
  1445. intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType);
  1446. intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
  1447. intent.putExtra(AccountManager.KEY_USERDATA, loginName);
  1448. /// add user data to the new account; TODO probably can be done in the last parameter
  1449. // addAccountExplicitly, or in KEY_USERDATA
  1450. mAccountMgr.setUserData(mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion());
  1451. mAccountMgr.setUserData(mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl);
  1452. ArrayList<Object> authResultData = authResult.getData();
  1453. if (authResultData == null || authResultData.size() == 0) {
  1454. Log_OC.e(this, "Could not read user data!");
  1455. return false;
  1456. }
  1457. UserInfo userInfo = (UserInfo) authResultData.get(0);
  1458. mAccountMgr.setUserData(mAccount, Constants.KEY_DISPLAY_NAME, userInfo.getDisplayName());
  1459. mAccountMgr.setUserData(mAccount, Constants.KEY_USER_ID, userInfo.getId());
  1460. mAccountMgr.setUserData(mAccount, Constants.KEY_OC_ACCOUNT_VERSION,
  1461. Integer.toString(UserAccountManager.ACCOUNT_VERSION_WITH_PROPER_ID));
  1462. setAccountAuthenticatorResult(intent.getExtras());
  1463. setResult(RESULT_OK, intent);
  1464. return true;
  1465. }
  1466. }
  1467. public void onScan() {
  1468. if (PermissionUtil.checkSelfPermission(this, Manifest.permission.CAMERA)) {
  1469. startQRScanner();
  1470. } else {
  1471. PermissionUtil.requestCameraPermission(this);
  1472. }
  1473. }
  1474. private void startQRScanner() {
  1475. Intent i = new Intent(this, QrCodeActivity.class);
  1476. startActivityForResult(i, REQUEST_CODE_QR_SCAN);
  1477. }
  1478. @Override
  1479. public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
  1480. @NonNull int[] grantResults) {
  1481. switch (requestCode) {
  1482. case PermissionUtil.PERMISSIONS_CAMERA: {
  1483. // If request is cancelled, result arrays are empty.
  1484. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  1485. // permission was granted
  1486. startQRScanner();
  1487. } else {
  1488. // permission denied
  1489. return;
  1490. }
  1491. return;
  1492. }
  1493. default:
  1494. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  1495. }
  1496. }
  1497. /**
  1498. /**
  1499. * Updates the content and visibility state of the icon and text associated
  1500. * to the last check on the ownCloud server.
  1501. */
  1502. private void showServerStatus() {
  1503. if (mServerStatusIcon == NO_ICON && EMPTY_STRING.equals(mServerStatusText)) {
  1504. mServerStatusView.setVisibility(View.INVISIBLE);
  1505. } else {
  1506. mServerStatusView.setText(mServerStatusText);
  1507. mServerStatusView.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon, 0, 0, 0);
  1508. mServerStatusView.setVisibility(View.VISIBLE);
  1509. }
  1510. }
  1511. /**
  1512. * Updates the content and visibility state of the icon and text associated
  1513. * to the interactions with the OAuth authorization server.
  1514. */
  1515. private void showAuthStatus() {
  1516. if (mAuthStatusIcon == NO_ICON && EMPTY_STRING.equals(mAuthStatusText)) {
  1517. mAuthStatusView.setVisibility(View.INVISIBLE);
  1518. } else {
  1519. mAuthStatusView.setText(mAuthStatusText);
  1520. mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
  1521. mAuthStatusView.setVisibility(View.VISIBLE);
  1522. }
  1523. }
  1524. private void showRefreshButton(boolean show) {
  1525. if (webViewLoginMethod && mRefreshButton != null) {
  1526. if (show) {
  1527. mRefreshButton.setVisibility(View.VISIBLE);
  1528. } else {
  1529. mRefreshButton.setVisibility(View.GONE);
  1530. }
  1531. }
  1532. }
  1533. /**
  1534. * Called when the eye icon in the password field is clicked.
  1535. *
  1536. * Toggles the visibility of the password in the field.
  1537. */
  1538. public void onViewPasswordClick() {
  1539. int selectionStart = mPasswordInput.getSelectionStart();
  1540. int selectionEnd = mPasswordInput.getSelectionEnd();
  1541. if (isPasswordVisible()) {
  1542. hidePassword();
  1543. } else {
  1544. showPassword();
  1545. }
  1546. mPasswordInput.setSelection(selectionStart, selectionEnd);
  1547. }
  1548. /**
  1549. * Called when the 'action' button in an IME is pressed ('enter' in software keyboard).
  1550. *
  1551. * Used to trigger the authentication check when the user presses 'enter' after writing the
  1552. * password, or to throw the server test when the only field on screen is the URL input field.
  1553. */
  1554. @Override
  1555. public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) {
  1556. if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null &&
  1557. inputField.equals(mPasswordInput)) {
  1558. if (mOkButton.isEnabled()) {
  1559. mOkButton.performClick();
  1560. }
  1561. } else if ((actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_NULL)
  1562. && inputField != null && inputField.equals(mHostUrlInput)) {
  1563. checkOcServer();
  1564. }
  1565. return false; // always return false to grant that the software keyboard is hidden anyway
  1566. }
  1567. private abstract static class RightDrawableOnTouchListener implements OnTouchListener {
  1568. private static final int RIGHT_DRAWABLE_COMPOUND_DRAWABLES_LENGTH = 2;
  1569. private int fuzz = 75;
  1570. /**
  1571. * {@inheritDoc}
  1572. */
  1573. @Override
  1574. public boolean onTouch(View view, MotionEvent event) {
  1575. Drawable rightDrawable = null;
  1576. if (view instanceof TextView) {
  1577. Drawable[] drawables = ((TextView) view).getCompoundDrawables();
  1578. if (drawables.length > RIGHT_DRAWABLE_COMPOUND_DRAWABLES_LENGTH) {
  1579. rightDrawable = drawables[2];
  1580. }
  1581. }
  1582. if (rightDrawable != null) {
  1583. final int x = (int) event.getX();
  1584. final int y = (int) event.getY();
  1585. final Rect bounds = rightDrawable.getBounds();
  1586. if (x >= (view.getRight() - bounds.width() - fuzz) &&
  1587. x <= (view.getRight() - view.getPaddingRight() + fuzz) &&
  1588. y >= (view.getPaddingTop() - fuzz) &&
  1589. y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
  1590. return onDrawableTouch(event);
  1591. }
  1592. }
  1593. return false;
  1594. }
  1595. public abstract boolean onDrawableTouch(final MotionEvent event);
  1596. }
  1597. /**
  1598. * Show untrusted cert dialog
  1599. */
  1600. public void showUntrustedCertDialog(X509Certificate x509Certificate, SslError error, SslErrorHandler handler) {
  1601. // Show a dialog with the certificate info
  1602. SslUntrustedCertDialog dialog;
  1603. if (x509Certificate == null) {
  1604. dialog = SslUntrustedCertDialog.newInstanceForEmptySslError(error, handler);
  1605. } else {
  1606. dialog = SslUntrustedCertDialog.newInstanceForFullSslError(x509Certificate, error, handler);
  1607. }
  1608. FragmentManager fm = getSupportFragmentManager();
  1609. FragmentTransaction ft = fm.beginTransaction();
  1610. ft.addToBackStack(null);
  1611. dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
  1612. }
  1613. /**
  1614. * Show untrusted cert dialog
  1615. */
  1616. private void showUntrustedCertDialog(RemoteOperationResult result) {
  1617. // Show a dialog with the certificate info
  1618. SslUntrustedCertDialog dialog = SslUntrustedCertDialog.
  1619. newInstanceForFullSslError((CertificateCombinedException) result.getException());
  1620. FragmentManager fm = getSupportFragmentManager();
  1621. FragmentTransaction ft = fm.beginTransaction();
  1622. ft.addToBackStack(null);
  1623. dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG);
  1624. }
  1625. private void doOnResumeAndBound() {
  1626. //Log_OC.e(TAG, "registering to listen for operation callbacks" );
  1627. mOperationsServiceBinder.addOperationListener(this, mHandler);
  1628. if (mWaitingForOpId <= Integer.MAX_VALUE) {
  1629. mOperationsServiceBinder.dispatchResultIfFinished((int) mWaitingForOpId, this);
  1630. }
  1631. if (!webViewLoginMethod && !TextUtils.isEmpty(mHostUrlInput.getText()) && !mServerIsChecked) {
  1632. checkOcServer();
  1633. }
  1634. }
  1635. private void dismissDialog(String dialogTag) {
  1636. Fragment frag = getSupportFragmentManager().findFragmentByTag(dialogTag);
  1637. if (frag instanceof DialogFragment) {
  1638. DialogFragment dialog = (DialogFragment) frag;
  1639. try {
  1640. dialog.dismiss();
  1641. } catch (IllegalStateException e) {
  1642. Log_OC.e(TAG, e.getMessage());
  1643. dialog.dismissAllowingStateLoss();
  1644. }
  1645. }
  1646. }
  1647. /**
  1648. * Implements callback methods for service binding.
  1649. */
  1650. private class OperationsServiceConnection implements ServiceConnection {
  1651. @Override
  1652. public void onServiceConnected(ComponentName component, IBinder service) {
  1653. if (component.equals(
  1654. new ComponentName(AuthenticatorActivity.this, OperationsService.class)
  1655. )) {
  1656. mOperationsServiceBinder = (OperationsServiceBinder) service;
  1657. Uri data = getIntent().getData();
  1658. if (data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme))) {
  1659. String prefix = getString(R.string.login_data_own_scheme) + PROTOCOL_SUFFIX + "login/";
  1660. LoginUrlInfo loginUrlInfo = parseLoginDataUrl(prefix, data.toString());
  1661. if (loginUrlInfo != null) {
  1662. try {
  1663. mServerInfo.mBaseUrl = AuthenticatorUrlUtils.normalizeUrlSuffix(loginUrlInfo.serverAddress);
  1664. webViewUser = loginUrlInfo.username;
  1665. webViewPassword = loginUrlInfo.password;
  1666. doOnResumeAndBound();
  1667. } catch (Exception e) {
  1668. mServerStatusIcon = R.drawable.ic_alert;
  1669. mServerStatusText = "QR Code could not be read!";
  1670. showServerStatus();
  1671. }
  1672. }
  1673. } else {
  1674. doOnResumeAndBound();
  1675. }
  1676. }
  1677. }
  1678. @Override
  1679. public void onServiceDisconnected(ComponentName component) {
  1680. if (component.equals(
  1681. new ComponentName(AuthenticatorActivity.this, OperationsService.class)
  1682. )) {
  1683. Log_OC.e(TAG, "Operations service crashed");
  1684. mOperationsServiceBinder = null;
  1685. }
  1686. }
  1687. }
  1688. /**
  1689. * Create and show dialog for request authentication to the user
  1690. *
  1691. * @param webView Web view to embed into the authentication dialog.
  1692. * @param handler Object responsible for catching and recovering HTTP authentication fails.
  1693. */
  1694. public void createAuthenticationDialog(WebView webView, HttpAuthHandler handler) {
  1695. // Show a dialog with the certificate info
  1696. CredentialsDialogFragment dialog = CredentialsDialogFragment.newInstanceForCredentials(webView, handler);
  1697. FragmentManager fm = getSupportFragmentManager();
  1698. FragmentTransaction ft = fm.beginTransaction();
  1699. ft.addToBackStack(null);
  1700. dialog.setCancelable(false);
  1701. dialog.show(ft, CREDENTIALS_DIALOG_TAG);
  1702. if (!mIsFirstAuthAttempt) {
  1703. DisplayUtils.showSnackMessage(this, R.string.saml_authentication_wrong_pass);
  1704. } else {
  1705. mIsFirstAuthAttempt = false;
  1706. }
  1707. }
  1708. /**
  1709. * For retrieving the clicking on authentication cancel button.
  1710. */
  1711. public void doNegativeAuthenticationDialogClick() {
  1712. mIsFirstAuthAttempt = true;
  1713. }
  1714. @Override
  1715. protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  1716. if (requestCode == REQUEST_CODE_QR_SCAN) {
  1717. if (data == null) {
  1718. return;
  1719. }
  1720. String result = data.getStringExtra("com.blikoon.qrcodescanner.got_qr_scan_relult");
  1721. if (result == null || !result.startsWith(getString(R.string.login_data_own_scheme))) {
  1722. mServerStatusIcon = R.drawable.ic_alert;
  1723. mServerStatusText = "QR Code could not be read!";
  1724. showServerStatus();
  1725. return;
  1726. }
  1727. parseAndLoginFromWebView(result);
  1728. }
  1729. }
  1730. /**
  1731. * Obtain the X509Certificate from SslError
  1732. *
  1733. * @param error SslError
  1734. * @return X509Certificate from error
  1735. */
  1736. public static X509Certificate getX509CertificateFromError(SslError error) {
  1737. Bundle bundle = SslCertificate.saveState(error.getCertificate());
  1738. X509Certificate x509Certificate;
  1739. byte[] bytes = bundle.getByteArray("x509-certificate");
  1740. if (bytes == null) {
  1741. x509Certificate = null;
  1742. } else {
  1743. try {
  1744. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  1745. Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
  1746. x509Certificate = (X509Certificate) cert;
  1747. } catch (CertificateException e) {
  1748. x509Certificate = null;
  1749. }
  1750. }
  1751. return x509Certificate;
  1752. }
  1753. /**
  1754. * Called from SslValidatorDialog when a new server certificate was correctly saved.
  1755. */
  1756. public void onSavedCertificate() {
  1757. checkOcServer();
  1758. }
  1759. /**
  1760. * Called from SslValidatorDialog when a new server certificate could not be saved when the user requested it.
  1761. */
  1762. @Override
  1763. public void onFailedSavingCertificate() {
  1764. DisplayUtils.showSnackMessage(this, R.string.ssl_validator_not_saved);
  1765. }
  1766. }