From 20df6dcc042b52a06155dfefe809021996623f96 Mon Sep 17 00:00:00 2001
From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:52:35 -0700
Subject: [PATCH] Use a webview for WP.com login
---
.../login/LoginPrologueRevampedFragment.kt | 2 +-
WordPress/src/main/AndroidManifest.xml | 13 +++-
.../android/ui/accounts/LoginActivity.java | 18 ++++-
.../ui/accounts/UnifiedLoginTracker.kt | 1 +
.../accounts/login/LoginPrologueListener.kt | 4 +-
.../ui/accounts/login/WPcomLoginHelper.kt | 51 +++++++++++++
.../accounts/login/LoginPrologueFragment.kt | 2 +-
.../login/LoginPrologueRevampedFragment.kt | 4 +-
.../network/rest/wpapi/WPcomLoginClient.kt | 75 +++++++++++++++++++
.../WPcomAuthorizationCodeResponse.kt | 9 +++
.../android/fluxc/store/AccountStore.java | 6 ++
11 files changed, 178 insertions(+), 7 deletions(-)
create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt
create mode 100644 libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPcomLoginClient.kt
create mode 100644 libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/WPcomAuthorizationCodeResponse.kt
diff --git a/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt b/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt
index 597a8d55a5a5..c7108f67870a 100644
--- a/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt
+++ b/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt
@@ -56,7 +56,7 @@ class LoginPrologueRevampedFragment : Fragment() {
LoginScreenRevamped(
onWpComLoginClicked = {
viewModel.onWpComLoginClicked()
- loginPrologueListener.showEmailLoginScreen()
+ loginPrologueListener.showEmailLoginScreen(this.context)
},
onSiteAddressLoginClicked = {
viewModel.onSiteAddressLoginClicked()
diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml
index 01638ac32ba7..addb6bfd20e3 100644
--- a/WordPress/src/main/AndroidManifest.xml
+++ b/WordPress/src/main/AndroidManifest.xml
@@ -113,7 +113,18 @@
+ android:windowSoftInputMode="adjustResize"
+ android:exported="true">
+
+
+
+
+
+
+
+
mDispatchingAndroidInjector;
@Inject protected LoginAnalyticsListener mLoginAnalyticsListener;
@@ -144,6 +148,14 @@ private enum SmartLockHelperState {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // Attempt Login if this activity was created in response to a user confirming login
+ mLoginHelper.tryLoginWithDataString(getIntent().getDataString());
+
+ if (mLoginHelper.isLoggedIn()) {
+ this.loggedInAndFinish(new ArrayList(), true);
+ return;
+ }
+
LoginFlowThemeHelper.injectMissingCustomAttributes(getTheme());
setContentView(R.layout.login_activity);
@@ -452,8 +464,10 @@ private void startLogin() {
// LoginPrologueListener implementation methods
@Override
- public void showEmailLoginScreen() {
- checkSmartLockPasswordAndStartLogin();
+ public void showEmailLoginScreen(@NonNull Context context) {
+ Intent loginWithWPcom = new Intent(Intent.ACTION_VIEW, mLoginHelper.loginUri());
+ mUnifiedLoginTracker.setFlowAndStep(Flow.WORDPRESS_COM_WEB, Step.START);
+ context.startActivity(loginWithWPcom);
}
@Override
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt
index ebc0f143f3ea..58d6efde4d1f 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt
@@ -126,6 +126,7 @@ class UnifiedLoginTracker
enum class Flow(val value: String) {
PROLOGUE("prologue"),
WORDPRESS_COM("wordpress_com"),
+ WORDPRESS_COM_WEB("wordpress_com_web"),
GOOGLE_LOGIN("google_login"),
SMART_LOCK_LOGIN("smart_lock_login"),
LOGIN_MAGIC_LINK("login_magic_link"),
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt
index 2ea009c72b6e..9a5cb9aa42aa 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt
@@ -1,7 +1,9 @@
package org.wordpress.android.ui.accounts.login
+import android.content.Context
+
interface LoginPrologueListener {
// Login Prologue callbacks
- fun showEmailLoginScreen()
+ fun showEmailLoginScreen(context: Context)
fun loginViaSiteAddress()
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt
new file mode 100644
index 000000000000..86d9b9561296
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt
@@ -0,0 +1,51 @@
+package org.wordpress.android.ui.accounts.login
+
+import android.net.Uri
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import org.wordpress.android.BuildConfig
+import org.wordpress.android.fluxc.network.rest.wpapi.WPcomLoginClient
+import org.wordpress.android.fluxc.store.AccountStore
+import org.wordpress.android.util.config.AppConfig
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+class WPcomLoginHelper @Inject constructor(
+ private val loginClient: WPcomLoginClient,
+ private val accountStore: AccountStore,
+ private val appConfig: AppConfig
+) {
+ private val context: CoroutineContext = Dispatchers.IO
+
+ fun loginUri(): Uri {
+ return loginClient.loginUri(BuildConfig.OAUTH_REDIRECT_URI)
+ }
+
+ fun tryLoginWithDataString(data: String?) {
+ if (data == null) {
+ return
+ }
+
+ val code = this.codeFromAuthorizationUri(data) ?: return
+
+ runBlocking {
+ val tokenResult = loginClient.exchangeAuthCodeForToken(code, BuildConfig.OAUTH_REDIRECT_URI)
+ accountStore.updateAccessToken(tokenResult.getOrThrow())
+ Log.i("WPCOM_LOGIN", "Login Successful")
+ }
+ }
+
+ fun isLoggedIn(): Boolean {
+ return accountStore.hasAccessToken()
+ }
+
+ fun dispose() {
+ context.cancel()
+ }
+
+ private fun codeFromAuthorizationUri(string: String): String? {
+ return Uri.parse(string).getQueryParameter("code")
+ }
+}
diff --git a/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueFragment.kt b/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueFragment.kt
index 337f05a08b3f..446006d6e47e 100644
--- a/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueFragment.kt
+++ b/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueFragment.kt
@@ -56,7 +56,7 @@ class LoginPrologueFragment : Fragment(R.layout.login_signup_screen) {
continueWithWpcomButton.setOnClickListener {
unifiedLoginTracker.trackClick(Click.CONTINUE_WITH_WORDPRESS_COM)
- loginPrologueListener.showEmailLoginScreen()
+ loginPrologueListener.showEmailLoginScreen(view.context)
}
enterYourSiteAddressButton.setOnClickListener {
diff --git a/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt b/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt
index 0b8817da3b64..950054d9fa18 100644
--- a/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt
+++ b/WordPress/src/wordpress/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt
@@ -46,7 +46,9 @@ class LoginPrologueRevampedFragment : Fragment() {
setContent {
AppThemeM2 {
LoginScreenRevamped(
- onWpComLoginClicked = loginPrologueListener::showEmailLoginScreen,
+ onWpComLoginClicked = {
+ loginPrologueListener.showEmailLoginScreen(this.context)
+ },
onSiteAddressLoginClicked = loginPrologueListener::loginViaSiteAddress,
)
}
diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPcomLoginClient.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPcomLoginClient.kt
new file mode 100644
index 000000000000..f38163d7043a
--- /dev/null
+++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPcomLoginClient.kt
@@ -0,0 +1,75 @@
+package org.wordpress.android.fluxc.network.rest.wpapi
+
+import android.net.Uri
+import android.util.Log
+import com.google.gson.Gson
+import kotlinx.coroutines.withContext
+import okhttp3.FormBody
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.WPcomAuthorizationCodeResponse
+import org.wordpress.android.fluxc.network.rest.wpcom.auth.AppSecrets
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.coroutines.CoroutineContext
+
+@Singleton
+class WPcomLoginClient @Inject constructor(
+ private val context: CoroutineContext,
+ private val appSecrets: AppSecrets
+) {
+ private val client = OkHttpClient()
+
+ fun loginUri(redirectUri: String): Uri {
+ return Uri.Builder().scheme("https")
+ .authority("public-api.wordpress.com")
+ .path("/oauth2/authorize")
+ .appendQueryParameter("client_id", appSecrets.appId)
+ .appendQueryParameter("redirect_uri", redirectUri)
+ .appendQueryParameter("response_type", "code")
+ .appendQueryParameter("scope", "global")
+ .build()
+ }
+
+ suspend fun exchangeAuthCodeForToken(code: String, redirectUri: String): Result {
+ val tokenUrl = Uri.Builder()
+ .scheme("https")
+ .authority("public-api.wordpress.com")
+ .path("oauth2/token")
+ .build()
+ .toString()
+
+ val formBody = FormBody.Builder()
+
+ mutableMapOf(
+ "client_id" to appSecrets.appId,
+ "redirect_uri" to redirectUri,
+ "client_secret" to appSecrets.appSecret,
+ "code" to code,
+ "grant_type" to "authorization_code",
+ ).forEach { (t, u) -> formBody.add(t, u) }
+
+ val request = Request.Builder()
+ .url(tokenUrl)
+ .post(formBody.build())
+ .build()
+
+ return withContext(context) {
+ val response = client.newCall(request).execute()
+
+ if (!response.isSuccessful) {
+ response.body?.let { Log.e("WPCOM_LOGIN", it.string()) }
+ Result.failure(WPcomLoginError.AccessDenied)
+ } else {
+ val json = response.body?.string() ?: return@withContext Result.failure(WPcomLoginError.InvalidResponse)
+ val gson = Gson().fromJson(json, WPcomAuthorizationCodeResponse::class.java)
+ Result.success(gson.access_token)
+ }
+ }
+ }
+}
+
+sealed class WPcomLoginError(val code: Int): Throwable() {
+ data object AccessDenied: WPcomLoginError(1)
+ data object InvalidResponse: WPcomLoginError(2)
+}
diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/WPcomAuthorizationCodeResponse.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/WPcomAuthorizationCodeResponse.kt
new file mode 100644
index 000000000000..6b604e78beb5
--- /dev/null
+++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/WPcomAuthorizationCodeResponse.kt
@@ -0,0 +1,9 @@
+package org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords
+
+data class WPcomAuthorizationCodeResponse(
+ val access_token: String,
+ val token_type: String,
+ val blog_id: String,
+ val blog_url: String,
+ val scope: String
+)
diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/AccountStore.java b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/AccountStore.java
index 7e8e63c3288d..cb7a115a6987 100644
--- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/AccountStore.java
+++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/AccountStore.java
@@ -1325,6 +1325,12 @@ public String getAccessToken() {
return mAccessToken.get();
}
+ // Ths is the preferred way to authenticate a WordPress.com or Jetpack user in the app
+ public void updateAccessToken(@NonNull String token) {
+ mAccessToken.set(token);
+ emitChange(new OnAuthenticationChanged());
+ }
+
private void updateToken(UpdateTokenPayload updateTokenPayload) {
mAccessToken.set(updateTokenPayload.token);
emitChange(new OnAuthenticationChanged());