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

Offer to store Account Number in password manager #7737

Merged
Merged
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
6 changes: 5 additions & 1 deletion android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ Line wrap the file at 100 chars. Th
* **Security**: in case of vulnerabilities.

## [Unreleased]

### Added
- Prompt password manager to store new account number on account creation.

### Changed
- Disable Wireguard port setting when a obfuscation is selected since it is not used when an
- Disable Wireguard port setting when a obfuscation is selected since it is not used when an
obfuscation is applied.

### Removed
Expand Down
3 changes: 2 additions & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,9 @@ dependencies {
implementation(libs.commons.validator)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.datastore)
implementation(libs.androidx.ktx)
implementation(libs.androidx.coresplashscreen)
implementation(libs.androidx.credentials)
implementation(libs.androidx.ktx)
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App.Starting"
tools:ignore="GoogleAppIndexingWarning">
tools:ignore="CredManMissingDal,CredentialDependency,GoogleAppIndexingWarning">
<!--
MainActivity
Must be exported in order to be launchable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.CredentialManager
import androidx.credentials.exceptions.CreateCredentialException
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.dropUnlessResumed
import co.touchlab.kermit.Logger
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.NavGraphs
Expand Down Expand Up @@ -140,6 +144,17 @@ fun Welcome(
snackbarHostState.showSnackbarImmediately(
message = context.getString(R.string.error_occurred)
)
is WelcomeViewModel.UiSideEffect.StoreCredentialsRequest -> {
// UserId is not allowed to be empty
val createPasswordRequest =
CreatePasswordRequest(id = "-", password = uiSideEffect.accountNumber.value)
val credentialsManager = CredentialManager.create(context)
try {
credentialsManager.createCredential(context, createPasswordRequest)
} catch (e: CreateCredentialException) {
Logger.w("Unable to create Credentials")
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.WelcomeUiState
import net.mullvad.mullvadvpn.lib.common.util.isAfterNowInstant
import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
Expand All @@ -39,12 +40,18 @@ class WelcomeViewModel(
val uiState =
combine(
connectionProxy.tunnelState,
deviceRepository.deviceState.filterNotNull(),
deviceRepository.deviceState.filterNotNull().onEach {
viewModelScope.launch {
it.accountNumber()?.let { accountNumber ->
_uiSideEffect.send(UiSideEffect.StoreCredentialsRequest(accountNumber))
}
}
},
paymentUseCase.paymentAvailability,
) { tunnelState, accountState, paymentAvailability ->
WelcomeUiState(
tunnelState = tunnelState,
accountNumber = accountState.token(),
accountNumber = accountState.accountNumber(),
deviceName = accountState.displayName(),
showSitePayment = !isPlayBuild,
billingPaymentState = paymentAvailability?.toPaymentState(),
Expand Down Expand Up @@ -122,6 +129,8 @@ class WelcomeViewModel(

data object OpenConnectScreen : UiSideEffect

data class StoreCredentialsRequest(val accountNumber: AccountNumber) : UiSideEffect

data object GenericError : UiSideEffect
}

Expand Down
2 changes: 2 additions & 0 deletions android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ android-volley = "1.2.1"
androidx-activitycompose = "1.10.1"
androidx-appcompat = "1.7.0"
androidx-ktx = "1.15.0"
androidx-credentials = "1.3.0"
androidx-coresplashscreen = "1.1.0-rc01"
androidx-datastore = "1.1.3"
androidx-espresso = "3.6.1"
Expand Down Expand Up @@ -85,6 +86,7 @@ android-volley = { module = "com.android.volley:volley", version.ref = "android-
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activitycompose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-coresplashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-coresplashscreen" }
androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidx-credentials" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }
androidx-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
androidx-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-ktx" }
Expand Down
14 changes: 14 additions & 0 deletions android/gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
<trusting group="androidx.collection"/>
<trusting group="androidx.constraintlayout"/>
<trusting group="androidx.core"/>
<trusting group="androidx.credentials" name="credentials"/>
<trusting group="androidx.databinding"/>
<trusting group="androidx.datastore"/>
<trusting group="androidx.fragment"/>
Expand Down Expand Up @@ -413,6 +414,11 @@
<sha256 value="9516c2ae44284ea0bd3d0eade0ee638879b708cbe31e3af92ba96c300604ebc3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.5.0">
<artifact name="annotation-1.5.0.module">
<sha256 value="4c84feee2db891ff6b97d613a0d40ab96ce297b034a6927ca8479f09e82d7c2e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.6.0">
<artifact name="annotation-1.6.0.module">
<sha256 value="6146b6138643b2ac0590df509dd51abaea769c79fd7602eb217168fe5af78cd2" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -1477,6 +1483,14 @@
<sha256 value="11386cfa46cbbfddb6a4059f14354c00691cf65d3d63c3618818a83326ef3c7f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.credentials" name="credentials" version="1.3.0">
<artifact name="credentials-1.3.0.aar">
<sha256 value="b33c1a3e2d41fc3a163dd161d3334d2510d9b2086ed923c60a6f79ee22b78984" origin="Generated by Gradle"/>
</artifact>
<artifact name="credentials-1.3.0.module">
<sha256 value="bd5d6f9628aa958f03823e7a913a83cf11a403e5df2150491f4ce7e1684a708a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.cursoradapter" name="cursoradapter" version="1.0.0">
<artifact name="cursoradapter-1.0.0.aar">
<sha256 value="a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564" origin="Generated by Gradle"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ sealed class DeviceState : Parcelable {
return (this as? LoggedIn)?.device?.displayName()
}

fun token(): AccountNumber? {
fun accountNumber(): AccountNumber? {
return (this as? LoggedIn)?.accountNumber
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,15 @@ class AppInteractor(
device.findObjectWithTimeout(By.desc("Remove")).click()
clickActionButtonByText("Yes, log out device")
}

fun dismissStorePasswordPromptIfShown() {
try {
device.waitForIdle()
val selector = By.textContains("password")
device.wait(Until.hasObject(selector), DEFAULT_TIMEOUT)
device.pressBack()
} catch (e: IllegalArgumentException) {
// This is OK since it means the password prompt wasn't shown.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class CreateAccountMockApiTest : MockApiTest() {
app.waitForLoginPrompt()
app.attemptCreateAccount()

app.dismissStorePasswordPromptIfShown()

// Assert
val expectedResult = "1234 1234 1234 1234"
app.ensureAccountCreated(expectedResult)
Expand Down