mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 03:08:50 +03:00
Add WebAuthCallbackActivity to handle hCaptcha callbacks (#705)
This commit is contained in:
parent
49ff8a761d
commit
e3547f4e13
7 changed files with 172 additions and 71 deletions
|
@ -237,6 +237,7 @@ koverReport {
|
|||
// OS-level components
|
||||
"com.x8bit.bitwarden.BitwardenApplication",
|
||||
"com.x8bit.bitwarden.MainActivity*",
|
||||
"com.x8bit.bitwarden.WebAuthCallbackActivity*",
|
||||
"com.x8bit.bitwarden.data.autofill.BitwardenAutofillService*",
|
||||
"com.x8bit.bitwarden.data.push.BitwardenFirebaseMessagingService*",
|
||||
// Empty Composables
|
||||
|
|
|
@ -34,6 +34,23 @@
|
|||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".WebAuthCallbackActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
@ -44,15 +61,6 @@
|
|||
android:host="captcha-callback"
|
||||
android:scheme="bitwarden" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
|
@ -50,39 +49,34 @@ class MainViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleFirstIntentReceived(action: MainAction.ReceiveFirstIntent) {
|
||||
val shareData = intentManager.getShareDataFromIntent(action.intent)
|
||||
when {
|
||||
shareData != null -> {
|
||||
authRepository.specialCircumstance =
|
||||
UserState.SpecialCircumstance.ShareNewSend(
|
||||
data = shareData,
|
||||
shouldFinishWhenComplete = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
handleIntent(
|
||||
intent = action.intent,
|
||||
isFirstIntent = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNewIntentReceived(action: MainAction.ReceiveNewIntent) {
|
||||
val captchaCallbackTokenResult = action.intent.getCaptchaCallbackTokenResult()
|
||||
val shareData = intentManager.getShareDataFromIntent(action.intent)
|
||||
when {
|
||||
captchaCallbackTokenResult != null -> {
|
||||
authRepository.setCaptchaCallbackTokenResult(
|
||||
tokenResult = captchaCallbackTokenResult,
|
||||
)
|
||||
}
|
||||
handleIntent(
|
||||
intent = action.intent,
|
||||
isFirstIntent = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleIntent(
|
||||
intent: Intent,
|
||||
isFirstIntent: Boolean,
|
||||
) {
|
||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||
when {
|
||||
shareData != null -> {
|
||||
authRepository.specialCircumstance =
|
||||
UserState.SpecialCircumstance.ShareNewSend(
|
||||
data = shareData,
|
||||
// Allow users back into the already-running app when completing the
|
||||
// Send task.
|
||||
shouldFinishWhenComplete = false,
|
||||
// Send task when this is not the first intent.
|
||||
shouldFinishWhenComplete = isFirstIntent,
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* An activity to receive callbacks from Custom Chrome tabs or other web-auth related flows such
|
||||
* the current state of the task holding the [MainActivity] can remain undisturbed.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class WebAuthCallbackActivity : AppCompatActivity() {
|
||||
|
||||
private val webAuthCallbackViewModel: WebAuthCallbackViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
webAuthCallbackViewModel.trySendAction(
|
||||
WebAuthCallbackAction.IntentReceive(intent = intent),
|
||||
)
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||
}
|
||||
startActivity(intent)
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A view model that handles logic for the [WebAuthCallbackActivity].
|
||||
*/
|
||||
@HiltViewModel
|
||||
class WebAuthCallbackViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
) : BaseViewModel<Unit, Unit, WebAuthCallbackAction>(Unit) {
|
||||
override fun handleAction(action: WebAuthCallbackAction) {
|
||||
when (action) {
|
||||
is WebAuthCallbackAction.IntentReceive -> handleIntentReceived(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleIntentReceived(action: WebAuthCallbackAction.IntentReceive) {
|
||||
val captchaCallbackTokenResult = action.intent.getCaptchaCallbackTokenResult()
|
||||
when {
|
||||
captchaCallbackTokenResult != null -> {
|
||||
authRepository.setCaptchaCallbackTokenResult(
|
||||
tokenResult = captchaCallbackTokenResult,
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions for the [WebAuthCallbackViewModel].
|
||||
*/
|
||||
sealed class WebAuthCallbackAction {
|
||||
/**
|
||||
* Receive Intent by the application.
|
||||
*/
|
||||
data class IntentReceive(val intent: Intent) : WebAuthCallbackAction()
|
||||
}
|
|
@ -3,7 +3,6 @@ package com.x8bit.bitwarden
|
|||
import android.content.Intent
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -13,14 +12,10 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
|||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class MainViewModelTest : BaseViewModelTest() {
|
||||
|
@ -32,7 +27,6 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { activeUserId } returns USER_ID
|
||||
every { specialCircumstance } returns null
|
||||
every { specialCircumstance = any() } just runs
|
||||
every { setCaptchaCallbackTokenResult(any()) } just runs
|
||||
}
|
||||
private val settingsRepository = mockk<SettingsRepository> {
|
||||
every { appTheme } returns AppTheme.DEFAULT
|
||||
|
@ -42,16 +36,6 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
every { getShareDataFromIntent(any()) } returns null
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockkStatic(Intent::getCaptchaCallbackTokenResult)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(Intent::getCaptchaCallbackTokenResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on AppThemeChanged should update state`() {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -102,29 +86,6 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with captcha host should call setCaptchaCallbackToken`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
every {
|
||||
mockIntent.getCaptchaCallbackTokenResult()
|
||||
} returns CaptchaCallbackTokenResult.Success(
|
||||
token = "mockk_token",
|
||||
)
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveNewIntent(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
verify {
|
||||
authRepository.setCaptchaCallbackTokenResult(
|
||||
tokenResult = CaptchaCallbackTokenResult.Success(
|
||||
token = "mockk_token",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class WebAuthCallbackViewModelTest : BaseViewModelTest() {
|
||||
private val authRepository = mockk<AuthRepository> {
|
||||
every { setCaptchaCallbackTokenResult(any()) } just runs
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockkStatic(Intent::getCaptchaCallbackTokenResult)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(Intent::getCaptchaCallbackTokenResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with captcha host should call setCaptchaCallbackToken`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
every {
|
||||
mockIntent.getCaptchaCallbackTokenResult()
|
||||
} returns CaptchaCallbackTokenResult.Success(
|
||||
token = "mockk_token",
|
||||
)
|
||||
viewModel.trySendAction(
|
||||
WebAuthCallbackAction.IntentReceive(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
verify {
|
||||
authRepository.setCaptchaCallbackTokenResult(
|
||||
tokenResult = CaptchaCallbackTokenResult.Success(
|
||||
token = "mockk_token",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel() = WebAuthCallbackViewModel(
|
||||
authRepository = authRepository,
|
||||
)
|
||||
}
|
Loading…
Add table
Reference in a new issue