-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use a webview for WP.com login #21414
base: trunk
Are you sure you want to change the base?
Changes from all commits
d8b9ea1
eb3d0d9
e2e9e16
8c8a89d
ed0fa69
2ab83b1
2824d28
f315a0d
921abfe
0fbfecd
355774f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,10 +113,21 @@ | |
<activity | ||
android:name=".ui.accounts.LoginActivity" | ||
android:theme="@style/LoginTheme.TransparentSystemBars" | ||
android:windowSoftInputMode="adjustResize" /> | ||
android:windowSoftInputMode="adjustResize" | ||
android:exported="true"> | ||
Comment on lines
+116
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're missing jetpack-launchmode.mp4
Once you add that you should handle the |
||
|
||
<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" | ||
Check warning Code scanning / Android Lint Application has custom scheme intent filters with missing autoVerify attributes Warning
Custom scheme intent filters should explicitly set the autoVerify attribute to true
|
||
android:theme="@style/NoDisplay" | ||
android:exported="true"> | ||
|
||
|
@@ -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> | ||
|
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; | ||
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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(); | ||
Comment on lines
+475
to
+482
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for jumping in, but since I took a look I wanted to share. Using Custom Tabs with the magic link leads to a weird experience. Assuming you are not logged into WP in the web browser, you will have to open the email client to get the magic link. Tapping this magic link opens the web browser, not the Custom Tab which was confusing slightly. In the end, it worked, however, 3 apps were required to finish the process. |
||
|
||
intent.launchUrl(this, mLoginHelper.getWpcomLoginUri()); | ||
} | ||
|
||
@Override | ||
|
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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you'd want to check if the token belongs to the current user before saving it. Users can log in with any account from the login webpage. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be for first-time authentication only – I'm thinking we'll want a |
||
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 |
---|---|---|
|
@@ -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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this is an outdated version of the library. The latest is 1.8.0. |
||
androidx-camera = '1.4.1' | ||
androidx-cardview = '1.0.0' | ||
androidx-compose-bom = '2024.12.01' | ||
|
@@ -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" } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sonarcloud flags this as a security risk. Does it need to be exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that this was required in order to handle custom URL schemes, but I might be incorrect!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I suspect you're right about that. This Sonarcloud page mentions ways to fix this warning.