Skip to content

Commit

Permalink
NMC-3250 -> opening login flow in internal webview instead of externa…
Browse files Browse the repository at this point in the history
…l browser.
  • Loading branch information
surinder-tsys committed Aug 28, 2024
1 parent 8988567 commit 7abb9c9
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 211 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.nextcloud.common.NextcloudClient;
import com.nextcloud.utils.extensions.AccountExtensionsKt;
import com.nmc.android.ui.LauncherActivity;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
Expand Down Expand Up @@ -397,6 +398,10 @@ private String getAccountType() {

@Override
public void startAccountCreation(final Activity activity) {
// NMC-3278 fix
// Splash screen should be shown properly before navigating to Login screen
if(activity instanceof LauncherActivity) return;

Intent intent = new Intent(context, AuthenticatorActivity.class);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,21 @@
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;

import com.blikoon.qrcodescanner.QrCodeActivity;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nextcloud.android.common.ui.color.ColorUtil;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.nextcloud.client.onboarding.OnboardingService;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.common.PlainClient;
import com.nextcloud.operations.PostMethod;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
Expand Down Expand Up @@ -111,17 +104,13 @@
import com.owncloud.android.utils.theme.CapabilityUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;

import org.json.JSONObject;

import java.io.InputStream;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

Expand All @@ -137,16 +126,11 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.ProcessLifecycleOwner;
import de.cotech.hw.fido.WebViewFidoBridge;
import de.cotech.hw.fido.ui.FidoDialogOptions;
import de.cotech.hw.fido2.WebViewWebauthnBridge;
import de.cotech.hw.fido2.ui.WebauthnDialogOptions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import okhttp3.FormBody;
import okhttp3.RequestBody;

import static com.owncloud.android.utils.PermissionUtil.PERMISSIONS_CAMERA;

Expand Down Expand Up @@ -184,17 +168,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
private static final String KEY_USERNAME = "USERNAME";
private static final String KEY_PASSWORD = "PASSWORD";
private static final String KEY_ASYNC_TASK_IN_PROGRESS = "AUTH_IN_PROGRESS";

/**
* Login Flow v1
*/
// public static final String WEB_LOGIN = "/index.php/login/flow";

/**
* Login Flow v2
*/
public static final String WEB_LOGIN = "/index.php/login/v2";

public static final String WEB_LOGIN = "/index.php/login/flow";
public static final String PROTOCOL_SUFFIX = "://";
public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":";
public static final String HTTPS_PROTOCOL = "https://";
Expand Down Expand Up @@ -248,9 +222,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
@Inject PassCodeManager passCodeManager;
@Inject ViewThemeUtils.Factory viewThemeUtilsFactory;
@Inject ColorUtil colorUtil;
@Inject ClientFactory clientFactory;

private String token;

private boolean onlyAdd = false;
@SuppressLint("ResourceAsColor") @ColorInt
Expand All @@ -275,7 +246,7 @@ protected void onCreate(Bundle savedInstanceState) {
viewThemeUtils = viewThemeUtilsFactory.withPrimaryAsBackground();
viewThemeUtils.platform.themeStatusBar(this, ColorRole.PRIMARY);

// WebViewUtil webViewUtil = new WebViewUtil(this);
WebViewUtil webViewUtil = new WebViewUtil(this);

Uri data = getIntent().getData();
boolean directLogin = data != null && data.toString().startsWith(getString(R.string.login_data_own_scheme));
Expand Down Expand Up @@ -344,8 +315,7 @@ protected void onCreate(Bundle savedInstanceState) {
if (webViewLoginMethod) {
accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater());
setContentView(accountSetupWebviewBinding.getRoot());
anonymouslyPostLoginRequest(webloginUrl);
// initWebViewLogin(webloginUrl, false);
initWebViewLogin(webloginUrl, false);
} else {
accountSetupBinding = AccountSetupBinding.inflate(getLayoutInflater());
setContentView(accountSetupBinding.getRoot());
Expand All @@ -360,18 +330,10 @@ protected void onCreate(Bundle savedInstanceState) {
}

initServerPreFragment(savedInstanceState);
ProcessLifecycleOwner.get().getLifecycle().addObserver(lifecycleEventObserver);

// webViewUtil.checkWebViewVersion();
webViewUtil.checkWebViewVersion();
}

private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> {
if (event == Lifecycle.Event.ON_START && token != null) {
Log_OC.d(TAG, "Start poolLogin");
poolLogin();
}
});

private void deleteCookies() {
try {
CookieSyncManager.createInstance(this);
Expand All @@ -381,72 +343,11 @@ private void deleteCookies() {
}
}

private String baseUrl;

/**
* This function facilitates the login process by anonymously posting a login request to a specified URL.
* After posting the request, it retrieves the login URL for completing the login flow.
* The login flow version used is v2.
*
* @param url The URL where the login request is to be anonymously posted.
* This URL should handle the login request and return the login URL.
* It's typically the entry point for the login process.
* Example: "<a href="https://example.com/index.php/login/v2">...</a>"
*/
private void anonymouslyPostLoginRequest(String url) {
baseUrl = url;

Thread thread = new Thread(() -> {
String response = getResponseOfAnonymouslyPostLoginRequest();

try {
JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject();
String loginUrl = getLoginUrl(jsonObject);
runOnUiThread(() -> {
initLoginInfoView();
launchDefaultWebBrowser(loginUrl);
});
token = jsonObject.getAsJsonObject("poll").get("token").getAsString();
} catch (Throwable t) {
Log_OC.d(TAG, "Error caught at anonymouslyPostLoginRequest: " + t);
DisplayUtils.showSnackMessage(this, R.string.authenticator_activity_login_error);
}
});

thread.start();
}

private String getResponseOfAnonymouslyPostLoginRequest() {
PostMethod post = new PostMethod(baseUrl, false, new FormBody.Builder().build());
PlainClient client = clientFactory.createPlainClient();
post.execute(client);
return post.getResponseBodyAsString();
}

private String getLoginUrl(JsonObject response) {
String result = response.get("login").getAsString();
if (result == null) {
result = getResources().getString(R.string.webview_login_url);
}

return result;
}

private void launchDefaultWebBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

private static String getWebLoginUserAgent() {
return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL + " (Android)";
}

/**
* @Deprecated This function is deprecated. Please use the {@link #anonymouslyPostLoginRequest(String)} method instead, which utilizes the improved login flow v2.
*/
@Deprecated
@SuppressFBWarnings("ANDROID_WEB_VIEW_JAVASCRIPT")
@SuppressLint("SetJavaScriptEnabled")
private void initWebViewLogin(String baseURL, boolean useGenericUserAgent) {
Expand Down Expand Up @@ -806,8 +707,7 @@ protected void onNewIntent(Intent intent) {
if (intent.getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater());
setContentView(accountSetupWebviewBinding.getRoot());
anonymouslyPostLoginRequest(getString(R.string.provider_registration_server));
// initWebViewLogin(getString(R.string.provider_registration_server), true);
initWebViewLogin(getString(R.string.provider_registration_server), true);
}
}

Expand Down Expand Up @@ -1044,16 +944,7 @@ private void onGetServerInfoFinish(RemoteOperationResult result) {

accountSetupWebviewBinding = AccountSetupWebviewBinding.inflate(getLayoutInflater());
setContentView(accountSetupWebviewBinding.getRoot());

if (!isLoginProcessCompleted) {
if (!isRedirectedToTheDefaultBrowser) {
anonymouslyPostLoginRequest(mServerInfo.mBaseUrl + WEB_LOGIN);
isRedirectedToTheDefaultBrowser = true;
} else {
initLoginInfoView();
}
// initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false);
}
initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false);
}
} else {
updateServerStatusIconAndText(result);
Expand All @@ -1066,20 +957,6 @@ private void onGetServerInfoFinish(RemoteOperationResult result) {
}
}

// region LoginInfoView
private void initLoginInfoView() {
LinearLayout loginFlowLayout = accountSetupWebviewBinding.loginFlowV2.getRoot();
MaterialButton cancelButton = accountSetupWebviewBinding.loginFlowV2.cancelButton;
loginFlowLayout.setVisibility(View.VISIBLE);

cancelButton.setOnClickListener(v -> {
loginFlowExecutorService.shutdown();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver);
recreate();
});
}
// endregion

/**
* Chooses the right icon and text to show to the user for the received operation result.
*
Expand Down Expand Up @@ -1321,8 +1198,7 @@ public void onAuthenticatorTaskCallback(RemoteOperationResult<UserInfo> result)

} else { // authorization fail due to client side - probably wrong credentials
if (accountSetupWebviewBinding != null) {
anonymouslyPostLoginRequest(mServerInfo.mBaseUrl + WEB_LOGIN);
// initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false);
initWebViewLogin(mServerInfo.mBaseUrl + WEB_LOGIN, false);
DisplayUtils.showSnackMessage(this,
accountSetupWebviewBinding.loginWebview, R.string.auth_access_failed,
result.getLogMessage());
Expand Down Expand Up @@ -1666,71 +1542,6 @@ public void onServiceDisconnected(ComponentName component) {
}
}

private final ScheduledExecutorService loginFlowExecutorService = Executors.newSingleThreadScheduledExecutor();
private boolean isLoginProcessCompleted = false;
private boolean isRedirectedToTheDefaultBrowser = false;

private void poolLogin() {
loginFlowExecutorService.scheduleWithFixedDelay(() -> {
if (!isLoginProcessCompleted) {
performLoginFlowV2();
}
}, 0, 30, TimeUnit.SECONDS);
}

private void performLoginFlowV2() {
String postRequestUrl = baseUrl + "/poll";

RequestBody requestBody = new FormBody.Builder()
.add("token", token)
.build();

PlainClient client = clientFactory.createPlainClient();
PostMethod post = new PostMethod(postRequestUrl, false, requestBody);
int status = post.execute(client);
String response = post.getResponseBodyAsString();

Log_OC.d(TAG, "performLoginFlowV2 status: " + status);
Log_OC.d(TAG, "performLoginFlowV2 response: " + response);

if (!response.isEmpty()) {
runOnUiThread(() -> completeLoginFlow(response, status));
}
}

private void completeLoginFlow(String response, int status) {
try {
JSONObject jsonObject = new JSONObject(response);

String server = jsonObject.getString("server");
String loginName = jsonObject.getString("loginName");
String appPassword = jsonObject.getString("appPassword");

LoginUrlInfo loginUrlInfo = new LoginUrlInfo();
loginUrlInfo.serverAddress = server;
loginUrlInfo.username = loginName;
loginUrlInfo.password = appPassword;

isLoginProcessCompleted = (status == 200 && !server.isEmpty() && !loginName.isEmpty() && !appPassword.isEmpty());

if (accountSetupBinding != null) {
accountSetupBinding.hostUrlInput.setText("");
}
mServerInfo.mBaseUrl = AuthenticatorUrlUtils.INSTANCE.normalizeUrlSuffix(loginUrlInfo.serverAddress);
webViewUser = loginUrlInfo.username;
webViewPassword = loginUrlInfo.password;
} catch (Exception e) {
Log_OC.d(TAG, "Error caught at completeLoginFlow: " + e);
mServerStatusIcon = R.drawable.ic_alert;
mServerStatusText = getString(R.string.qr_could_not_be_read);
showServerStatus();
}

checkOcServer();
loginFlowExecutorService.shutdown();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver);
}

/**
* Called from SslValidatorDialog when a new server certificate was correctly saved.
*/
Expand Down
20 changes: 5 additions & 15 deletions app/src/main/res/layout/account_setup_webview.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,21 @@
~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
android:layout_height="match_parent">

<!-- Login Flow V1 -->
<WebView
android:visibility="gone"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/login_webview">
</WebView>

<!-- Login Flow V2 -->
<include
tools:visibility="visible"
android:visibility="gone"
android:id="@+id/login_flow_v2"
layout="@layout/login_flow_info_layout_v2" />

<ProgressBar
android:id="@+id/login_webview_progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
android:indeterminate="true"/>
</FrameLayout>

0 comments on commit 7abb9c9

Please sign in to comment.