Skip to content

Commit

Permalink
Use a webview for WP.com login (#21414)
Browse files Browse the repository at this point in the history
* Use a webview for WP.com login

* Use Custom Chrome Tabs

* Rename showEmailLoginScreen->showWPcomLoginScreen

* Use showWPcomLoginScreen to reauthenticate

* Don’t compile if secrets are missing

* Use the latest secrets.properties

* Changed start and exit animations

* Preload the login page

* Fix Detekt issues

* Move redirect URI into build.grade

It’s not a secret

* Fix merge issue

---------

Co-authored-by: Nick Bradbury <[email protected]>
  • Loading branch information
jkmassel and nbradbury authored Jan 24, 2025
1 parent 8d43dbe commit 8b98532
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 20 deletions.
4 changes: 2 additions & 2 deletions .configure
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"project_name": "WordPress-Android",
"branch": "trunk",
"pinned_hash": "11fa9c59619ba8d31d0bce2b7ba4412074c00d69",
"pinned_hash": "1c83426e3f9175a6d561f4af53c69a1e2097a6ef",
"files_to_copy": [
{
"file": "android/WPAndroid/secrets.properties",
Expand Down Expand Up @@ -47,4 +47,4 @@
"file_dependencies": [

]
}
}
Binary file modified .configure-files/secrets.properties.enc
Binary file not shown.
4 changes: 4 additions & 0 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ android {
buildConfigField "String", "PUSH_NOTIFICATIONS_APP_KEY", '"org.wordpress.android"'
buildConfigField "boolean", "ENABLE_QRCODE_AUTH_FLOW", "false"
buildConfigField "boolean", "ENABLE_OPEN_WEB_LINKS_WITH_JP_FLOW", "true"
buildConfigField "String", "WPCOM_REDIRECT_URI", '"wordpress://wpcom-authorize"'

manifestPlaceholders = [magicLinkScheme:"wordpress"]
}
Expand All @@ -229,6 +230,7 @@ android {
buildConfigField "String", "PUSH_NOTIFICATIONS_APP_KEY", '"com.jetpack.android"'
buildConfigField "boolean", "ENABLE_QRCODE_AUTH_FLOW", "true"
buildConfigField "boolean", "ENABLE_OPEN_WEB_LINKS_WITH_JP_FLOW", "false"
buildConfigField "String", "WPCOM_REDIRECT_URI", '"jetpack://wpcom-authorize"'

manifestPlaceholders = [magicLinkScheme:"jetpack"]

Expand Down Expand Up @@ -443,6 +445,8 @@ dependencies {
implementation(libs.google.mlkit.text.recognition)
implementation(libs.google.mlkit.barcode.scanning.main)

implementation(libs.androidx.browser)

// CameraX
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class LoginPrologueRevampedFragment : Fragment() {
LoginScreenRevamped(
onWpComLoginClicked = {
viewModel.onWpComLoginClicked()
loginPrologueListener.showEmailLoginScreen()
loginPrologueListener.showWPcomLoginScreen(this.context)
},
onSiteAddressLoginClicked = {
viewModel.onSiteAddressLoginClicked()
Expand Down
16 changes: 15 additions & 1 deletion WordPress/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,18 @@
<activity
android:name=".ui.accounts.LoginActivity"
android:theme="@style/LoginTheme.TransparentSystemBars"
android:windowSoftInputMode="adjustResize" />
android:windowSoftInputMode="adjustResize"
android:exported="true">

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="wpcom-authorize"
android:scheme="${magicLinkScheme}" />
</intent-filter>
</activity>

<activity
android:name=".ui.accounts.LoginMagicLinkInterceptActivity"
Expand Down Expand Up @@ -1155,6 +1166,9 @@
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<!-- required for Android 11 (API level 30) or higher -->
<package android:name="com.jetpack.android" />
</queries>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import android.content.Context;
import android.util.Base64;

import androidx.annotation.NonNull;

import com.goterl.lazysodium.utils.Key;

import org.wordpress.android.BuildConfig;
Expand All @@ -19,12 +21,13 @@
import dagger.hilt.android.qualifiers.ApplicationContext;
import dagger.hilt.components.SingletonComponent;


@InstallIn(SingletonComponent.class)
@Module
public class AppConfigModule {
@Provides
@NonNull @Provides
public AppSecrets provideAppSecrets() {
return new AppSecrets(BuildConfig.OAUTH_APP_ID, BuildConfig.OAUTH_APP_SECRET);
return new AppSecrets(BuildConfig.OAUTH_APP_ID, BuildConfig.OAUTH_APP_SECRET, BuildConfig.WPCOM_REDIRECT_URI);
}

@Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.wordpress.android.ui.accounts;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
Expand All @@ -9,6 +10,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
Expand Down Expand Up @@ -58,8 +60,10 @@
import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Click;
import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Flow;
import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Source;
import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Step;
import org.wordpress.android.ui.accounts.login.LoginPrologueListener;
import org.wordpress.android.ui.accounts.login.LoginPrologueRevampedFragment;
import org.wordpress.android.ui.accounts.login.WPcomLoginHelper;
import org.wordpress.android.ui.accounts.login.jetpack.LoginNoSitesFragment;
import org.wordpress.android.ui.accounts.login.jetpack.LoginSiteCheckErrorFragment;
import org.wordpress.android.ui.main.ChooseSiteActivity;
Expand Down Expand Up @@ -130,6 +134,7 @@ private enum SmartLockHelperState {

private LoginMode mLoginMode;
private LoginViewModel mViewModel;
@Inject protected WPcomLoginHelper mLoginHelper;

@Inject DispatchingAndroidInjector<Object> mDispatchingAndroidInjector;
@Inject protected LoginAnalyticsListener mLoginAnalyticsListener;
Expand All @@ -144,6 +149,18 @@ 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());

// Start preloading the WordPress.com login page if needed – this avoids visual hitches
// when displaying that screen
mLoginHelper.bindCustomTabsService(this);

if (mLoginHelper.isLoggedIn()) {
this.loggedInAndFinish(new ArrayList<Integer>(), true);
return;
}

LoginFlowThemeHelper.injectMissingCustomAttributes(getTheme());

setContentView(R.layout.login_activity);
Expand Down Expand Up @@ -184,7 +201,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
break;
case WPCOM_REAUTHENTICATE:
mUnifiedLoginTracker.setSource(Source.REAUTHENTICATION);
checkSmartLockPasswordAndStartLogin();
showWPcomLoginScreen(getBaseContext());
break;
case SHARE_INTENT:
mUnifiedLoginTracker.setSource(Source.SHARE);
Expand Down Expand Up @@ -451,9 +468,20 @@ private void startLogin() {

// LoginPrologueListener implementation methods

@Override
public void showEmailLoginScreen() {
checkSmartLockPasswordAndStartLogin();
public void showWPcomLoginScreen(@NonNull Context context) {
AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_WPCOM_WEBVIEW);
mUnifiedLoginTracker.setFlowAndStep(Flow.WORDPRESS_COM_WEB, Step.WPCOM_WEB_START);

CustomTabsIntent intent = new CustomTabsIntent.Builder()
.setShareState(CustomTabsIntent.SHARE_STATE_OFF)
.setStartAnimations(this, R.anim.activity_slide_in_from_right, R.anim.activity_slide_out_to_left)
.setExitAnimations(this, R.anim.activity_slide_in_from_left, R.anim.activity_slide_out_to_right)
.setUrlBarHidingEnabled(true)
.setInstantAppsEnabled(false)
.setShowTitle(false)
.build();

intent.launchUrl(this, mLoginHelper.getWpcomLoginUri());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sealed class LoginNavigationEvents {
data class SelectSite(val localId: Int) : LoginNavigationEvents()
object CreateNewSite : LoginNavigationEvents()
object CloseWithResultOk : LoginNavigationEvents()
object ShowEmailLoginScreen : LoginNavigationEvents()
object showWPcomLoginScreen : LoginNavigationEvents()
object ShowLoginViaSiteAddressScreen : LoginNavigationEvents()
object ShowJetpackIndividualPluginOverlay : LoginNavigationEvents()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -147,7 +148,8 @@ class UnifiedLoginTracker
SHOW_EMAIL_HINTS("show_email_hints"),
PASSWORD_CHALLENGE("password_challenge"),
NOT_A_JETPACK_SITE("not_a_jetpack_site"),
NO_JETPACK_SITES("no_jetpack_sites")
NO_JETPACK_SITES("no_jetpack_sites"),
WPCOM_WEB_START("wpcom_web_start")
}

enum class Click(val value: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.wordpress.android.ui.accounts.login

import android.content.Context

interface LoginPrologueListener {
// Login Prologue callbacks
fun showEmailLoginScreen()
fun showWPcomLoginScreen(context: Context)
fun loginViaSiteAddress()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.wordpress.android.ui.accounts.login

import android.content.ComponentName
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.browser.customtabs.CustomTabsCallback
import androidx.browser.customtabs.CustomTabsClient
import androidx.browser.customtabs.CustomTabsServiceConnection
import androidx.browser.customtabs.CustomTabsSession
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import org.wordpress.android.fluxc.network.rest.wpapi.WPcomLoginClient
import org.wordpress.android.fluxc.network.rest.wpcom.auth.AppSecrets
import org.wordpress.android.fluxc.store.AccountStore
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext

class WPcomLoginHelper @Inject constructor(
private val loginClient: WPcomLoginClient,
private val accountStore: AccountStore,
appSecrets: AppSecrets
) {
private val context: CoroutineContext = Dispatchers.IO

val wpcomLoginUri = loginClient.loginUri(appSecrets.redirectUri)
private val customTabsServiceConnection = ServiceConnection(wpcomLoginUri)

fun tryLoginWithDataString(data: String?) {
if (data == null) {
return
}

val code = this.codeFromAuthorizationUri(data) ?: return

runBlocking {
val tokenResult = loginClient.exchangeAuthCodeForToken(code)
accountStore.updateAccessToken(tokenResult.getOrThrow())
Log.i("WPCOM_LOGIN", "Login Successful")
}
}

fun isLoggedIn(): Boolean {
return accountStore.hasAccessToken()
}

fun dispose() {
context.cancel()
}

fun bindCustomTabsService(context: Context) {
customTabsServiceConnection.bind(context)
}

private fun codeFromAuthorizationUri(string: String): String? {
return Uri.parse(string).getQueryParameter("code")
}
}

class ServiceConnection(
var uri: Uri
): CustomTabsServiceConnection() {
private var client: CustomTabsClient? = null
private var session: CustomTabsSession? = null

override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
client.warmup(0)
this.client = client

val session = client.newSession(CustomTabsCallback())
session?.mayLaunchUrl(uri, null, null)
session?.mayLaunchUrl(Uri.parse("https://wordpress.com/log-in/"), null, null)

this.session = session
}

override fun onServiceDisconnected(name: ComponentName?) {
this.client = null
this.session = null
}

fun bind(context: Context) {
// Do nothing if there is an existing service connection
if (this.client != null) {
return
}

// Get the default browser package name, this will be null if
// the default browser does not provide a CustomTabsService
val packageName = CustomTabsClient.getPackageName(context, null) ?: return

CustomTabsClient.bindCustomTabsService(context, packageName, this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class LoginPrologueFragment : Fragment(R.layout.login_signup_screen) {

continueWithWpcomButton.setOnClickListener {
unifiedLoginTracker.trackClick(Click.CONTINUE_WITH_WORDPRESS_COM)
loginPrologueListener.showEmailLoginScreen()
loginPrologueListener.showWPcomLoginScreen(view.context)
}

enterYourSiteAddressButton.setOnClickListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ class LoginPrologueRevampedFragment : Fragment() {
setContent {
AppThemeM2 {
LoginScreenRevamped(
onWpComLoginClicked = loginPrologueListener::showEmailLoginScreen,
onWpComLoginClicked = {
loginPrologueListener.showWPcomLoginScreen(this.context)
},
onSiteAddressLoginClicked = loginPrologueListener::loginViaSiteAddress,
)
}
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ androidx-activity = '1.9.3'
androidx-annotation = '1.9.1'
androidx-appcompat = '1.7.0'
androidx-arch-core = '2.2.0'
androidx-browser = '1.5.0'
androidx-camera = '1.4.1'
androidx-cardview = '1.0.0'
androidx-compose-bom = '2024.12.01'
Expand Down Expand Up @@ -120,6 +121,7 @@ androidx-annotation = { group = "androidx.annotation", name = "annotation", vers
androidx-appcompat-main = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
androidx-appcompat-resources = { group = "androidx.appcompat", name = "appcompat-resources", version.ref = "androidx-appcompat" }
androidx-arch-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "androidx-arch-core" }
androidx-browser = { group="androidx.browser", "name" = "browser", version.ref = "androidx-browser" }
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "androidx-camera" }
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "androidx-camera" }
androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "androidx-camera" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ public enum Stat {
LOGIN_SOCIAL_2FA_NEEDED,
LOGIN_SOCIAL_ACCOUNTS_NEED_CONNECTING,
LOGIN_SOCIAL_ERROR_UNKNOWN_USER,
LOGIN_WPCOM_WEBVIEW,
LOGIN_WPCOM_BACKGROUND_SERVICE_UPDATE,
// This stat is part of a funnel that provides critical information. Before
// making ANY modification to this stat please refer to: p4qSXL-35X-p2
Expand Down
Loading

0 comments on commit 8b98532

Please sign in to comment.