Skip to content

Commit

Permalink
Use a webview for WP.com login
Browse files Browse the repository at this point in the history
  • Loading branch information
jkmassel committed Nov 4, 2024
1 parent cae7acb commit 20df6dc
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 7 deletions.
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.showEmailLoginScreen(this.context)
},
onSiteAddressLoginClicked = {
viewModel.onSiteAddressLoginClicked()
Expand Down
13 changes: 12 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
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 Down Expand Up @@ -58,8 +59,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 +133,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 +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<Integer>(), true);
return;
}

LoginFlowThemeHelper.injectMissingCustomAttributes(getTheme());

setContentView(R.layout.login_activity);
Expand Down Expand Up @@ -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
Expand Down
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 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 showEmailLoginScreen(context: Context)
fun loginViaSiteAddress()
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
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.showEmailLoginScreen(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.showEmailLoginScreen(this.context)
},
onSiteAddressLoginClicked = loginPrologueListener::loginViaSiteAddress,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> {
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)
}
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 20df6dc

Please sign in to comment.