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());