Skip to content
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

[PM-13425] Login request display on push #4343

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/src/main/java/com/x8bit/bitwarden/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavScreen
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval.navigateToLoginApproval
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
Expand Down Expand Up @@ -94,6 +95,13 @@ class MainActivity : AppCompatActivity() {
)
.show()
}

is MainEvent.NavigateToLoginApproval -> {
navController.navigateToLoginApproval(
fingerprint = "",
requestId = event.requestId,
)
}
}
}
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
Expand Down
24 changes: 17 additions & 7 deletions app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,16 @@ class MainViewModel @Inject constructor(
authRepository.switchAccount(passwordlessRequestData.userId)
}
}
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.PasswordlessRequest(
passwordlessRequestData = passwordlessRequestData,
// Allow users back into the already-running app when completing the
// autofill task when this is not the first intent.
shouldFinishWhenComplete = isFirstIntent,
)

sendEvent(MainEvent.NavigateToLoginApproval(passwordlessRequestData.loginRequestId))

// specialCircumstanceManager.specialCircumstance =
// SpecialCircumstance.PasswordlessRequest(
// passwordlessRequestData = passwordlessRequestData,
// // Allow users back into the already-running app when completing the
// // autofill task when this is not the first intent.
// shouldFinishWhenComplete = isFirstIntent,
// )
}

completeRegistrationData != null -> {
Expand Down Expand Up @@ -518,4 +521,11 @@ sealed class MainEvent {
* Show a toast with the given [message].
*/
data class ShowToast(val message: Text) : MainEvent()

/**
* Navigates to the Login Approval screen with the given fingerprint.
*/
data class NavigateToLoginApproval(
val requestId: String,
) : MainEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.util.createPasswordlessRequestDataIntent
import com.x8bit.bitwarden.data.autofill.util.toPendingIntentMutabilityFlag
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import com.x8bit.bitwarden.data.platform.manager.PushManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
Expand All @@ -28,6 +30,7 @@ class AuthRequestNotificationManagerImpl(
private val authDiskSource: AuthDiskSource,
pushManager: PushManager,
dispatcherManager: DispatcherManager,
private val appStateManager: AppStateManager,
) : AuthRequestNotificationManager {
private val ioScope = CoroutineScope(dispatcherManager.io)

Expand All @@ -40,6 +43,13 @@ class AuthRequestNotificationManagerImpl(

@SuppressLint("MissingPermission")
private fun handlePasswordlessRequestData(data: PasswordlessRequestData) {

val pendingIntent = createContentIntent(data)
if (appStateManager.appForegroundStateFlow.value == AppForegroundState.FOREGROUNDED) {
pendingIntent.send()
return
}

val notificationManager = NotificationManagerCompat.from(context)
// Construct the channel, calling this more than once is safe
notificationManager.createNotificationChannel(
Expand All @@ -54,7 +64,7 @@ class AuthRequestNotificationManagerImpl(
if (!notificationManager.areNotificationsEnabled(NOTIFICATION_CHANNEL_ID)) return
// Create the notification
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentIntent(createContentIntent(data))
.setContentIntent(pendingIntent)
.setContentTitle(context.getString(R.string.log_in_requested))
.setContentText(
authDiskSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManagerImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import com.x8bit.bitwarden.data.platform.manager.PushManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
Expand Down Expand Up @@ -49,12 +50,14 @@ object AuthManagerModule {
authDiskSource: AuthDiskSource,
pushManager: PushManager,
dispatcherManager: DispatcherManager,
appStateManager: AppStateManager,
): AuthRequestNotificationManager =
AuthRequestNotificationManagerImpl(
context = context,
authDiskSource = authDiskSource,
pushManager = pushManager,
dispatcherManager = dispatcherManager,
appStateManager = appStateManager,
)

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions

private const val FINGERPRINT: String = "fingerprint"
private const val REQUEST_ID: String = "requestId"
private const val LOGIN_APPROVAL_PREFIX = "login_approval"
private const val LOGIN_APPROVAL_ROUTE = "$LOGIN_APPROVAL_PREFIX?$FINGERPRINT={$FINGERPRINT}"
private const val LOGIN_APPROVAL_ROUTE =
"$LOGIN_APPROVAL_PREFIX?$FINGERPRINT={$FINGERPRINT}&$REQUEST_ID={$REQUEST_ID}"

/**
* Class to retrieve login approval arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class LoginApprovalArgs(val fingerprint: String?) {
data class LoginApprovalArgs(val fingerprint: String?, val requestId: String?) {
constructor(savedStateHandle: SavedStateHandle) : this(
fingerprint = savedStateHandle.get<String>(FINGERPRINT),
requestId = savedStateHandle.get<String>(REQUEST_ID),
)
}

Expand All @@ -37,6 +40,11 @@ fun NavGraphBuilder.loginApprovalDestination(
nullable = true
defaultValue = null
},
navArgument(REQUEST_ID) {
type = NavType.StringType
nullable = true
defaultValue = null
},
),
) {
LoginApprovalScreen(
Expand All @@ -50,7 +58,8 @@ fun NavGraphBuilder.loginApprovalDestination(
*/
fun NavController.navigateToLoginApproval(
fingerprint: String?,
requestId: String? = null,
navOptions: NavOptions? = null,
) {
navigate("$LOGIN_APPROVAL_PREFIX?$FINGERPRINT=$fingerprint", navOptions)
navigate("$LOGIN_APPROVAL_PREFIX?$FINGERPRINT=$fingerprint&$REQUEST_ID=$requestId", navOptions)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class LoginApprovalViewModel @Inject constructor(
?: requireNotNull(LoginApprovalArgs(savedStateHandle).fingerprint),
masterPasswordHash = null,
publicKey = "",
requestId = "",
requestId = LoginApprovalArgs(savedStateHandle).requestId.orEmpty(),
viewState = LoginApprovalState.ViewState.Loading,
dialogState = null,
)
Expand Down Expand Up @@ -86,11 +86,19 @@ class LoginApprovalViewModel @Inject constructor(
}
}
?: run {
authRepository
.getAuthRequestByFingerprintFlow(state.fingerprint)
.map { LoginApprovalAction.Internal.AuthRequestResultReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
if (state.requestId.isNotEmpty()) {
authRepository
.getAuthRequestByIdFlow(state.requestId)
.map { LoginApprovalAction.Internal.AuthRequestResultReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
} else {
authRepository
.getAuthRequestByIdFlow(state.fingerprint)
.map { LoginApprovalAction.Internal.AuthRequestResultReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
}
}
}

Expand Down
Loading