Compare commits

..

4 Commits

Author SHA1 Message Date
Álison Fernandes
bf2ac7cac9 Add pyproject.toml 2025-03-03 16:57:12 +00:00
Álison Fernandes
5bbbf7c5e4 Add script and tests for linked issues, removing method from the release notes script 2025-03-03 16:52:43 +00:00
Álison Fernandes
39d10fa77d Add release notes processing script and unit tests 2025-03-03 15:44:11 +00:00
Álison Fernandes
f0f240f8e5 Update gitignore for python 2025-03-03 15:43:27 +00:00
338 changed files with 2218 additions and 4912 deletions

View File

@@ -1,64 +0,0 @@
name: Passkey Bug Report
description: File a Passkey / FIDO2 related bug report
labels: [ "app:password-manager", "bug-passkey" ]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this Passkey-related bug report!
Please provide as much detail as possible to help us investigate the issue.
- type: dropdown
id: origin
attributes:
label: Origin
description: Are you using a web browser or a native application?
options:
- Web (Browser)
- Native Application (non-browser app)
validations:
required: true
- type: input
id: rp-id
attributes:
label: Web URL or App name
description: The website domain or app name you were trying to use the Passkey with
placeholder: "e.g. example.com or ExampleApp"
validations:
required: true
- type: checkboxes
id: operation-type
attributes:
label: Passkey Action
description: What passkey related action(s) were you trying to perform?
options:
- label: Creating new passkey (Registration)
- label: Signing in (Authentication)
validations:
required: true
- type: textarea
id: build-info
attributes:
label: Build Information
description: Please retrieve the build information from the About screen by tapping the Version number field
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Any additional context, steps to reproduce, error messages, or relevant information about the issue
- type: checkboxes
id: issue-tracking-info
attributes:
label: Issue Tracking Info
description: |
Issue tracking information
options:
- label: I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.

51
.github/renovate.json vendored
View File

@@ -1,56 +1,33 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>bitwarden/renovate-config"
],
"enabledManagers": [
"github-actions",
"gradle",
"bundler"
],
"extends": ["github>bitwarden/renovate-config"],
"enabledManagers": ["github-actions", "gradle", "bundler"],
"packageRules": [
{
"groupName": "gh minor",
"matchManagers": [
"github-actions"
],
"matchUpdateTypes": [
"minor",
"patch"
]
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "gradle minor",
"matchUpdateTypes": [
"minor",
"patch"
],
"matchManagers": [
"gradle"
]
"matchUpdateTypes": ["minor", "patch"],
"matchManagers": ["gradle"]
},
{
"groupName": "kotlin",
"description": "Kotlin and Compose dependencies that must be updated together to maintain compatibility.",
"matchManagers": [
"gradle"
"matchPackagePatterns": [
"androidx.compose:compose-bom",
"androidx.lifecycle:*",
"org.jetbrains.kotlin.*",
"com.google.devtools.ksp"
],
"matchPackageNames": [
"/androidx.compose:compose-bom/",
"/androidx.lifecycle:*/",
"/org.jetbrains.kotlin.*/",
"/com.google.devtools.ksp/"
]
"matchManagers": ["gradle"]
},
{
"groupName": "bundler minor",
"matchUpdateTypes": [
"minor",
"patch"
],
"matchManagers": [
"bundler"
]
"matchUpdateTypes": ["minor", "patch"],
"matchManagers": ["bundler"]
}
]
}

View File

@@ -0,0 +1,64 @@
import sys
import subprocess
from typing import List
def create_linked_issue_comment(repo_owner: str, repo_name: str, release_name: str, release_link: str, pr_numbers: List[int]) -> str:
if len(pr_numbers) == 0:
return ""
pr_links = [f"* https://github.com/{repo_owner}/{repo_name}/pull/{pr_number}" for pr_number in pr_numbers]
return f":shipit: Pull Request(s) linked to this issue released in [{release_name}]({release_link}):\n\n"+ "\n".join(pr_links)
def comment_linked_issues_in_pr(owner: str, repo: str, pr_number: int) -> None:
"""Use GitHub CLI to comment all issues linked to a PR.
"""
linked_issues = get_linked_issues(owner, repo, pr_number)
for issue_number in linked_issues:
comment_github_issue(owner, repo, issue_number, comment)
def comment_github_issue(owner: str, repo: str, issue_number: int, comment: str) -> None:
"""Use GitHub CLI to comment on an issue.
"""
subprocess.run([
'gh', 'issue', 'comment', str(issue_number), '--body', comment, '--repo', f'{owner}/{repo}'
], check=True)
def get_linked_issues(owner: str, repo: str, pr_number: int) -> List[int]:
"""Use GitHub CLI to retrieve linked issue numbers for a PR.
"""
query = """
query ($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
closingIssuesReferences(first: 100) {
nodes {
number
}
}
}
}
}
"""
try:
result = subprocess.run([
'gh', 'api', 'graphql',
'-F', f'owner={owner}',
'-F', f'repo={repo}',
'-F', f'pr={pr_number}',
'-f', f'query={query}',
'--jq', '.data.repository.pullRequest.closingIssuesReferences.nodes[].number'
], capture_output=True, text=True, check=True)
# Split output into lines and convert to integers
if result.stdout.strip():
return [int(num) for num in result.stdout.strip().split('\n')]
return []
except subprocess.CalledProcessError:
print(f"Error fetching linked issues for PR #{pr_number}")
return []

View File

@@ -0,0 +1,112 @@
import re
import sys
import subprocess
import json
from typing import List, Tuple
def extract_jira_tickets(line: str) -> List[str]:
# Find all Jira tickets in format ABC-123 (with any prefix/suffix)
return re.findall(r'[A-Z]+-\d+', line)
def extract_pr_numbers(line: str) -> List[str]:
# Match PR numbers from GitHub format (#123)
return re.findall(r'#(\d+)', line)
def process_line(line: str) -> str:
"""Process a single line from release notes by removing Jira tickets, conventional commit prefixes and other common patterns.
Args:
line: A single line from release notes
Returns:
Processed line with tickets and prefixes removed
Example:
>>> process_line("[ABC-123] feat(ui): Add new button")
"Add new button"
"""
original = line
# Remove Jira ticket patterns:
# line = re.sub(r'\[[A-Z]+-\d+\]', '', line) # [ABC-123] -> ""
# line = re.sub(r'[A-Z]+-\d+:\s', '', line) # ABC-123: -> ""
# line = re.sub(r'[A-Z]+-\d+\s-\s', '', line) # ABC-123 - -> ""
# Remove keywords and their variations
patterns = [
r'BACKPORT', # BACKPORT -> ""
r'[deps]:', # [deps]: -> ""
r'feat(?:\([^)]*\))?:', # feat: or feat(ui): -> ""
r'bug(?:\([^)]*\))?:', # bug: or bug(core): -> ""
r'ci(?:\([^)]*\))?:' # ci: or ci(workflow): -> ""
]
for pattern in patterns:
line = re.sub(pattern, '', line)
cleaned = line.strip()
if cleaned != original.strip():
print(f"Processed: {original.strip()} -> {cleaned}")
return cleaned
def process_file(input_file: str) -> Tuple[List[str], List[str], List[str]]:
jira_tickets: List[str] = []
pr_numbers: List[str] = []
processed_lines: List[str] = []
print("Processing file: ", input_file)
with open(input_file, 'r') as f:
for line in f:
line = line.strip()
should_process = line and not line.endswith(':')
if should_process:
tickets = extract_jira_tickets(line)
jira_tickets.extend(tickets)
prs = extract_pr_numbers(line)
pr_numbers.extend(prs)
processed_lines.append(process_line(line))
else:
processed_lines.append(line)
# Remove duplicates while preserving order
jira_tickets = list(dict.fromkeys(jira_tickets))
pr_numbers = list(dict.fromkeys(pr_numbers))
print("Jira tickets:", ",".join(jira_tickets))
print("PR numbers:", ",".join(pr_numbers))
print("Finished processing file: ", input_file)
return jira_tickets, pr_numbers, processed_lines
def save_results(jira_tickets: List[str], pr_numbers: List[str], processed_lines: List[str],
jira_file: str = 'jira_tickets.txt',
pr_file: str = 'pr_numbers.txt',
processed_file: str = 'processed_notes.txt') -> None:
with open(jira_file, 'w') as f:
f.write('\n'.join(jira_tickets))
with open(pr_file, 'w') as f:
f.write('\n'.join(pr_numbers))
with open(processed_file, 'w') as f:
f.write('\n'.join(processed_lines))
if __name__ == '__main__':
input_file = 'release_notes.txt'
jira_file = 'jira_tickets.txt'
pr_file = 'pr_numbers.txt'
processed_file = 'processed_notes.txt'
if len(sys.argv) >= 2:
input_file = sys.argv[1]
if len(sys.argv) >= 3:
jira_file = sys.argv[2]
if len(sys.argv) >= 4:
pr_file = sys.argv[3]
if len(sys.argv) >= 5:
processed_file = sys.argv[4]
jira_tickets, pr_numbers, processed_lines = process_file(input_file)
save_results(jira_tickets, pr_numbers, processed_lines, jira_file, pr_file, processed_file)

View File

@@ -0,0 +1,4 @@
[project]
name = "release-notes-processor"
description = "Process GitHub release notes to clean up formatting and extract relevant IDs."
requires-python = ">=3.13"

View File

@@ -0,0 +1,30 @@
import unittest
from linked_issues import get_linked_issues, create_linked_issue_comment
class TestLinkedIssues(unittest.TestCase):
def test_create_linked_issue_comment(self):
test_cases = [
("bitwarden", "android", "v2025.1.0", "https://github.com/bitwarden/android/releases/tag/v2025.1.0", [4696]),
("bitwarden", "android", "v2025.2.0", "https://github.com/bitwarden/android/releases/tag/v2025.2.0", [4809, 1, 2, 3]),
("bitwarden", "android", "v2025.3.0", "https://github.com/bitwarden/android/releases/tag/v2025.3.0", []),
]
for owner, repo, release_name, release_link, pr_numbers in test_cases:
with self.subTest(msg=f"Creating comment for issue in release {release_name}"):
comment = create_linked_issue_comment(owner, repo, release_name, release_link, pr_numbers)
print(comment + "\n")
def test_get_linked_issues(self):
test_cases = [
("bitwarden", "android", 4696, [4659]),
("bitwarden", "android", 4809, [])
]
for owner, repo, pr_id, expected_linked_issues in test_cases:
with self.subTest(msg=f"Testing PR #{pr_id} for {owner}/{repo}"):
result = get_linked_issues(owner, repo, pr_id)
self.assertEqual(sorted(result), sorted(expected_linked_issues))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,95 @@
import unittest
import tempfile
import os
from process_release_notes import extract_jira_tickets, extract_pr_numbers, process_line, process_file, get_linked_issues
class TestProcessReleaseNotes(unittest.TestCase):
def setUp(self):
self.test_file = tempfile.NamedTemporaryFile(delete=False)
def tearDown(self):
os.unlink(self.test_file.name)
def test_extract_jira_tickets(self):
test_cases = [
("[ABC-123] Some text", ["ABC-123"]),
("DEF-456: Some text", ["DEF-456"]),
("GHI-789 - Some text", ["GHI-789"]),
("Multiple [ABC-123] and DEF-456: tickets", ["ABC-123", "DEF-456"]),
("No tickets here", []),
("Mixed formats ABC-123 [DEF-456] GHI-789:", ["ABC-123", "DEF-456", "GHI-789"])
]
for input_text, expected in test_cases:
with self.subTest(input_text=input_text):
result = extract_jira_tickets(input_text)
self.assertEqual(result, expected)
def test_extract_pr_numbers(self):
test_cases = [
("PR #123 text", ["123"]),
("Multiple PRs #456 and #789", ["456", "789"]),
("No PR numbers", [])
]
for input_text, expected in test_cases:
with self.subTest(input_text=input_text):
result = extract_pr_numbers(input_text)
self.assertEqual(result, expected)
def test_process_line(self):
test_cases = [
("[ABC-123] BACKPORT Some text", "Some text"),
("DEF-456: feat(component): Some text", "Some text"),
("GHI-789 - bug(fix): Some text", "Some text"),
("ci: Some text", "Some text"),
("ci(workflow): Some text", "Some text"),
("feat: Direct feature", "Direct feature"),
("bug: Simple bugfix", "Simple bugfix"),
("Normal text", "Normal text")
]
for input_text, expected in test_cases:
with self.subTest(input_text=input_text):
result = process_line(input_text)
self.assertEqual(result, expected)
def test_process_file(self):
content = """
### Features:
[ABC-123] feat(comp): Feature 1 #123
DEF-456: bug(fix): Bug fix #456
GHI-789 - BACKPORT Some text #789
### Bug Fixes:
Another line without changes
"""
with open(self.test_file.name, 'w') as f:
f.write(content)
jira_tickets, pr_numbers, processed_lines = process_file(self.test_file.name)
self.assertEqual(jira_tickets, ["ABC-123", "DEF-456", "GHI-789"])
self.assertEqual(pr_numbers, ["123", "456", "789"])
self.assertEqual(processed_lines, [
'',
'### Features:',
'Feature 1 #123',
'Bug fix #456',
'Some text #789',
'',
'### Bug Fixes:',
'Another line without changes'
])
def test_get_linked_issues(self):
test_cases = [
("bitwarden", "android", 4696, [4659]),
("bitwarden", "android", 4809, [])
]
for owner, repo, pr_id, expected_linked_issues in test_cases:
with self.subTest(msg=f"Testing PR #{pr_id} for {owner}/{repo}"):
result = get_linked_issues(owner, repo, pr_id)
self.assertEqual(sorted(result), sorted(expected_linked_issues))
if __name__ == '__main__':
unittest.main()

6
.gitignore vendored
View File

@@ -28,3 +28,9 @@ user.properties
/app/src/standardBeta/google-services.json
/app/src/standardRelease/google-services.json
/authenticator/src/google-services.json
# python
__pycache__/
*.py[cod]
*$py.class
*.so

View File

@@ -9,18 +9,17 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.2)
aws-partitions (1.1067.0)
aws-sdk-core (3.220.1)
aws-eventstream (1.3.0)
aws-partitions (1.1040.0)
aws-sdk-core (3.216.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.99.0)
aws-sdk-kms (1.97.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.182.0)
aws-sdk-s3 (1.178.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@@ -35,7 +34,7 @@ GEM
highline (~> 2.0.0)
date (3.4.1)
declarative (0.0.20)
digest-crc (0.7.0)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.6.20240107)
dotenv (2.8.1)
@@ -70,7 +69,7 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.227.0)
fastlane (2.226.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -138,12 +137,12 @@ GEM
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.31.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.8.0)
google-cloud-core (1.7.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.5.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.47.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
@@ -161,17 +160,15 @@ GEM
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.9.0)
mutex_m
httpclient (2.8.3)
jmespath (1.6.2)
json (2.10.2)
json (2.9.1)
jwt (2.10.1)
base64
mini_magick (4.13.2)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.2.1)
nkf (0.2.0)
@@ -185,7 +182,7 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.1)
rexml (3.4.0)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)

View File

@@ -68,7 +68,7 @@ android {
buildConfigField(
type = "String",
name = "CI_INFO",
value = "${ciProperties.getOrDefault("ci.info", "\"local\"")}",
value = "${ciProperties.getOrDefault("ci.info", "\"local\"")}"
)
}
@@ -104,7 +104,7 @@ android {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
"proguard-rules.pro"
)
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
@@ -115,7 +115,7 @@ android {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
"proguard-rules.pro"
)
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
@@ -180,6 +180,7 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
@Suppress("UnstableApiUsage")
testOptions {
// Required for Robolectric
unitTests.isIncludeAndroidResources = true

View File

@@ -6,11 +6,6 @@ package com.x8bit.bitwarden
const val LEGACY_ACCESSIBILITY_SERVICE_NAME: String =
"com.x8bit.bitwarden.Accessibility.AccessibilityService"
/**
* The short form legacy name for the accessibility service.
*/
const val LEGACY_SHORT_ACCESSIBILITY_SERVICE_NAME: String = ".Accessibility.AccessibilityService"
/**
* The legacy name for the autofill service.
*/

View File

@@ -1,7 +1,6 @@
package com.x8bit.bitwarden
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
@@ -16,7 +15,6 @@ import androidx.compose.runtime.remember
import androidx.core.os.LocaleListCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
@@ -26,20 +24,16 @@ import com.x8bit.bitwarden.data.platform.manager.util.ObserveScreenDataEffect
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
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.ROOT_ROUTE
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavScreen
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.util.appLanguage
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* Primary entry point for the application.
*/
@Suppress("TooManyFunctions")
@OmitFromCoverage
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@@ -75,9 +69,13 @@ class MainActivity : AppCompatActivity() {
)
}
// Within the app the theme will change dynamically and will be managed by the
// Within the app the language and theme will change dynamically and will be managed by the
// OS, but we need to ensure we properly set the values when upgrading from older versions
// that handle this differently or when the activity restarts.
settingsRepository.appLanguage.localeName?.let { localeName ->
val localeList = LocaleListCompat.forLanguageTags(localeName)
AppCompatDelegate.setApplicationLocales(localeList)
}
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
setContent {
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
@@ -113,7 +111,7 @@ class MainActivity : AppCompatActivity() {
}
}
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
LocalManagerProvider(featureFlagsState = state.featureFlagsState) {
LocalManagerProvider {
ObserveScreenDataEffect(
onDataUpdate = remember(mainViewModel) {
{
@@ -124,19 +122,10 @@ class MainActivity : AppCompatActivity() {
},
)
BitwardenTheme(theme = state.theme) {
NavHost(
RootNavScreen(
onSplashScreenRemoved = { shouldShowSplashScreen = false },
navController = navController,
startDestination = ROOT_ROUTE,
) {
// Nothing else should end up at this top level, we just want the ability
// to have the debug menu appear on top of the rest of the app without
// interacting with the state-based navigation used by the RootNavScreen.
rootNavDestination { shouldShowSplashScreen = false }
debugMenuDestination(
onNavigateBack = { navController.popBackStack() },
onSplashScreenRemoved = { shouldShowSplashScreen = false },
)
}
)
}
}
}
@@ -151,31 +140,6 @@ class MainActivity : AppCompatActivity() {
)
}
override fun onResume() {
super.onResume()
// When the app resumes check for any app specific language which may have been
// set via the device settings. Similar to the theme setting in onCreate this
// ensures we properly set the values when upgrading from older versions
// that handle this differently or when the activity restarts.
val appSpecificLanguage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val locales: LocaleListCompat = AppCompatDelegate.getApplicationLocales()
if (locales.isEmpty) {
// App is using the system language
null
} else {
// App has specific language settings
locales.get(0)?.appLanguage
}
} else {
// For older versions, use what ever language is available from the repository.
settingsRepository.appLanguage
}
appSpecificLanguage?.let {
mainViewModel.trySendAction(MainAction.AppSpecificLanguageUpdate(it))
}
}
override fun onStop() {
super.onStop()
// In some scenarios on an emulator the Activity can leak when recreated

View File

@@ -19,12 +19,10 @@ import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
import com.x8bit.bitwarden.data.platform.manager.AppResumeManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManager
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
@@ -34,10 +32,8 @@ import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.model.FeatureFlagsState
import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
@@ -58,7 +54,6 @@ import java.time.Clock
import javax.inject.Inject
private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
private const val ANIMATION_REFRESH_DELAY = 500L
/**
* A view model that helps launch actions for the [MainActivity].
@@ -68,13 +63,12 @@ private const val ANIMATION_REFRESH_DELAY = 500L
class MainViewModel @Inject constructor(
accessibilitySelectionManager: AccessibilitySelectionManager,
autofillSelectionManager: AutofillSelectionManager,
featureFlagManager: FeatureFlagManager,
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
private val specialCircumstanceManager: SpecialCircumstanceManager,
private val garbageCollectionManager: GarbageCollectionManager,
private val fido2CredentialManager: Fido2CredentialManager,
private val intentManager: IntentManager,
private val settingsRepository: SettingsRepository,
settingsRepository: SettingsRepository,
private val vaultRepository: VaultRepository,
private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository,
@@ -85,9 +79,6 @@ class MainViewModel @Inject constructor(
initialState = MainState(
theme = settingsRepository.appTheme,
isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed,
isErrorReportingDialogEnabled = featureFlagManager.getFeatureFlag(
key = FlagKey.MobileErrorReporting,
),
),
) {
private var specialCircumstance: SpecialCircumstance?
@@ -105,12 +96,6 @@ class MainViewModel @Inject constructor(
.onEach { specialCircumstance = it }
.launchIn(viewModelScope)
featureFlagManager
.getFeatureFlagFlow(key = FlagKey.MobileErrorReporting)
.map { MainAction.Internal.OnMobileErrorReportingReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
accessibilitySelectionManager
.accessibilitySelectionFlow
.map { MainAction.Internal.AccessibilitySelectionReceive(it) }
@@ -149,7 +134,8 @@ class MainViewModel @Inject constructor(
// Switching between account states often involves some kind of animation (ex:
// account switcher) that we might want to give time to finish before triggering
// a refresh.
delay(ANIMATION_REFRESH_DELAY)
@Suppress("MagicNumber")
delay(500)
trySendAction(MainAction.Internal.CurrentUserStateChange)
}
.launchIn(viewModelScope)
@@ -161,7 +147,8 @@ class MainViewModel @Inject constructor(
is VaultStateEvent.Locked -> {
// Similar to account switching, triggering this action too soon can
// interfere with animations or navigation logic, so we will delay slightly.
delay(ANIMATION_REFRESH_DELAY)
@Suppress("MagicNumber")
delay(500)
trySendAction(MainAction.Internal.VaultUnlockStateChange)
}
@@ -185,17 +172,6 @@ class MainViewModel @Inject constructor(
}
override fun handleAction(action: MainAction) {
when (action) {
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
is MainAction.ResumeScreenDataReceived -> handleAppResumeDataUpdated(action)
is MainAction.AppSpecificLanguageUpdate -> handleAppSpecificLanguageUpdate(action)
is MainAction.Internal -> handleInternalAction(action)
}
}
private fun handleInternalAction(action: MainAction.Internal) {
when (action) {
is MainAction.Internal.AccessibilitySelectionReceive -> {
handleAccessibilitySelectionReceive(action)
@@ -209,24 +185,13 @@ class MainViewModel @Inject constructor(
is MainAction.Internal.ScreenCaptureUpdate -> handleScreenCaptureUpdate(action)
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
is MainAction.Internal.VaultUnlockStateChange -> handleVaultUnlockStateChange()
is MainAction.Internal.OnMobileErrorReportingReceive -> {
handleOnMobileErrorReportingReceive(action)
}
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
is MainAction.ResumeScreenDataReceived -> handleAppResumeDataUpdated(action)
}
}
private fun handleOnMobileErrorReportingReceive(
action: MainAction.Internal.OnMobileErrorReportingReceive,
) {
mutableStateFlow.update {
it.copy(isErrorReportingDialogEnabled = action.isErrorReportingEnabled)
}
}
private fun handleAppSpecificLanguageUpdate(action: MainAction.AppSpecificLanguageUpdate) {
settingsRepository.appLanguage = action.appLanguage
}
private fun handleAppResumeDataUpdated(action: MainAction.ResumeScreenDataReceived) {
when (val data = action.screenResumeData) {
null -> appResumeManager.clearResumeScreen()
@@ -480,16 +445,7 @@ class MainViewModel @Inject constructor(
data class MainState(
val theme: AppTheme,
val isScreenCaptureAllowed: Boolean,
private val isErrorReportingDialogEnabled: Boolean,
) : Parcelable {
/**
* Contains all feature flags that are available to the UI.
*/
val featureFlagsState: FeatureFlagsState
get() = FeatureFlagsState(
isErrorReportingDialogEnabled = isErrorReportingDialogEnabled,
)
}
) : Parcelable
/**
* Models actions for the [MainActivity].
@@ -515,12 +471,6 @@ sealed class MainAction {
*/
data class ResumeScreenDataReceived(val screenResumeData: AppResumeScreenData?) : MainAction()
/**
* Receive if there is an app specific locale selection made by user
* in the device's settings.
*/
data class AppSpecificLanguageUpdate(val appLanguage: AppLanguage) : MainAction()
/**
* Actions for internal use by the ViewModel.
*/
@@ -533,13 +483,6 @@ sealed class MainAction {
val cipherView: CipherView,
) : Internal()
/**
* Indicates the Mobile Error Reporting feature flag has been updated.
*/
data class OnMobileErrorReportingReceive(
val isErrorReportingEnabled: Boolean,
) : Internal()
/**
* Indicates the user has manually selected the given [cipherView] for autofill.
*/

View File

@@ -23,15 +23,6 @@ sealed class DeleteAccountResponseJson {
@Serializable
data class Invalid(
@SerialName("validationErrors")
private val validationErrors: Map<String, List<String?>>?,
) : DeleteAccountResponseJson() {
/**
* A human readable error message.
*/
val message: String?
get() = validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
}
val validationErrors: Map<String, List<String?>>?,
) : DeleteAccountResponseJson()
}

View File

@@ -16,7 +16,6 @@ import com.x8bit.bitwarden.data.auth.manager.model.AuthRequestsUpdatesResult
import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult
import com.x8bit.bitwarden.data.auth.manager.util.isSso
import com.x8bit.bitwarden.data.auth.manager.util.toAuthRequestTypeJson
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.data.platform.util.flatMap
@@ -52,9 +51,7 @@ class AuthRequestManagerImpl(
override fun getAuthRequestsWithUpdates(): Flow<AuthRequestsUpdatesResult> = flow {
while (currentCoroutineContext().isActive) {
when (val result = getAuthRequests()) {
is AuthRequestsResult.Error -> {
emit(AuthRequestsUpdatesResult.Error(error = result.error))
}
AuthRequestsResult.Error -> emit(AuthRequestsUpdatesResult.Error)
is AuthRequestsResult.Success -> {
emit(AuthRequestsUpdatesResult.Update(authRequests = result.authRequests))
@@ -73,8 +70,9 @@ class AuthRequestManagerImpl(
email = email,
authRequestType = authRequestType.toAuthRequestTypeJson(),
)
.getOrElse {
emit(CreateAuthRequestResult.Error(error = it))
.getOrNull()
?: run {
emit(CreateAuthRequestResult.Error)
return@flow
}
var authRequest = initialResult.authRequest
@@ -105,7 +103,7 @@ class AuthRequestManagerImpl(
)
}
.fold(
onFailure = { emit(CreateAuthRequestResult.Error(error = it)) },
onFailure = { emit(CreateAuthRequestResult.Error) },
onSuccess = { updateAuthRequest ->
when {
updateAuthRequest.requestApproved -> {
@@ -184,7 +182,7 @@ class AuthRequestManagerImpl(
)
}
.fold(
onFailure = { emit(AuthRequestUpdatesResult.Error(error = it)) },
onFailure = { emit(AuthRequestUpdatesResult.Error) },
onSuccess = { updateAuthRequest ->
when {
updateAuthRequest.requestApproved -> {
@@ -220,18 +218,13 @@ class AuthRequestManagerImpl(
fingerprint: String,
): Flow<AuthRequestUpdatesResult> = getAuthRequest {
when (val authRequestsResult = getAuthRequests()) {
is AuthRequestsResult.Error -> {
AuthRequestUpdatesResult.Error(error = authRequestsResult.error)
}
AuthRequestsResult.Error -> AuthRequestUpdatesResult.Error
is AuthRequestsResult.Success -> {
authRequestsResult
.authRequests
.firstOrNull { it.fingerprint == fingerprint }
?.let { AuthRequestUpdatesResult.Update(it) }
?: AuthRequestUpdatesResult.Error(
error = IllegalStateException("Could not find the auth request."),
)
?: AuthRequestUpdatesResult.Error
}
}
}
@@ -241,28 +234,30 @@ class AuthRequestManagerImpl(
): Flow<AuthRequestUpdatesResult> = getAuthRequest {
authRequestsService
.getAuthRequest(requestId)
.mapCatching { response ->
getFingerprintPhrase(response.publicKey)
.getOrThrow()
.let { fingerprint ->
AuthRequest(
id = response.id,
publicKey = response.publicKey,
platform = response.platform,
ipAddress = response.ipAddress,
key = response.key,
masterPasswordHash = response.masterPasswordHash,
creationDate = response.creationDate,
responseDate = response.responseDate,
requestApproved = response.requestApproved ?: false,
originUrl = response.originUrl,
fingerprint = fingerprint,
)
}
.map { response ->
getFingerprintPhrase(response.publicKey).getOrNull()?.let { fingerprint ->
AuthRequest(
id = response.id,
publicKey = response.publicKey,
platform = response.platform,
ipAddress = response.ipAddress,
key = response.key,
masterPasswordHash = response.masterPasswordHash,
creationDate = response.creationDate,
responseDate = response.responseDate,
requestApproved = response.requestApproved ?: false,
originUrl = response.originUrl,
fingerprint = fingerprint,
)
}
}
.fold(
onFailure = { AuthRequestUpdatesResult.Error(error = it) },
onSuccess = { AuthRequestUpdatesResult.Update(authRequest = it) },
onFailure = { AuthRequestUpdatesResult.Error },
onSuccess = { authRequest ->
authRequest
?.let { AuthRequestUpdatesResult.Update(it) }
?: AuthRequestUpdatesResult.Error
},
)
}
@@ -314,7 +309,7 @@ class AuthRequestManagerImpl(
}
}
.fold(
onFailure = { AuthRequestsResult.Error(error = it) },
onFailure = { AuthRequestsResult.Error },
onSuccess = { AuthRequestsResult.Success(authRequests = it) },
)
@@ -324,7 +319,7 @@ class AuthRequestManagerImpl(
publicKey: String,
isApproved: Boolean,
): AuthRequestResult {
val userId = activeUserId ?: return AuthRequestResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return AuthRequestResult.Error
return vaultSdkSource
.getAuthRequestKey(
publicKey = publicKey,
@@ -355,7 +350,7 @@ class AuthRequestManagerImpl(
)
}
.fold(
onFailure = { AuthRequestResult.Error(error = it) },
onFailure = { AuthRequestResult.Error },
onSuccess = { AuthRequestResult.Success(authRequest = it) },
)
}
@@ -467,7 +462,7 @@ class AuthRequestManagerImpl(
publicKey: String,
): Result<String> {
val profile = authDiskSource.userState?.activeAccount?.profile
?: return NoActiveUserException().asFailure()
?: return IllegalStateException("No active account").asFailure()
return authSdkSource.getUserFingerprint(
email = profile.email,
publicKey = publicKey,

View File

@@ -14,7 +14,5 @@ sealed class AuthRequestResult {
/**
* There was an error getting the user's auth requests.
*/
data class Error(
val error: Throwable,
) : AuthRequestResult()
data object Error : AuthRequestResult()
}

View File

@@ -19,9 +19,7 @@ sealed class AuthRequestUpdatesResult {
/**
* There was an error getting the user's auth requests.
*/
data class Error(
val error: Throwable,
) : AuthRequestUpdatesResult()
data object Error : AuthRequestUpdatesResult()
/**
* The auth request has been declined.

View File

@@ -14,7 +14,5 @@ sealed class AuthRequestsResult {
/**
* There was an error getting the user's auth requests.
*/
data class Error(
val error: Throwable,
) : AuthRequestsResult()
data object Error : AuthRequestsResult()
}

View File

@@ -14,7 +14,5 @@ sealed class AuthRequestsUpdatesResult {
/**
* There was an error getting the user's auth requests.
*/
data class Error(
val error: Throwable,
) : AuthRequestsUpdatesResult()
data object Error : AuthRequestsUpdatesResult()
}

View File

@@ -23,9 +23,7 @@ sealed class CreateAuthRequestResult {
/**
* There was a generic error getting the user's auth requests.
*/
data class Error(
val error: Throwable,
) : CreateAuthRequestResult()
data object Error : CreateAuthRequestResult()
/**
* The auth request has been declined.

View File

@@ -99,8 +99,6 @@ import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.datasource.network.util.isSslHandShakeError
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.LogsManager
@@ -469,7 +467,7 @@ class AuthRepositoryImpl(
masterPassword: String,
): DeleteAccountResult {
val profile = authDiskSource.userState?.activeAccount?.profile
?: return DeleteAccountResult.Error(message = null, error = NoActiveUserException())
?: return DeleteAccountResult.Error(message = null)
mutableHasPendingAccountDeletionStateFlow.value = true
return authSdkSource
.hashPassword(
@@ -503,13 +501,18 @@ class AuthRepositoryImpl(
fold(
onFailure = {
clearPendingAccountDeletion()
DeleteAccountResult.Error(error = it, message = null)
DeleteAccountResult.Error(message = null)
},
onSuccess = { response ->
when (response) {
is DeleteAccountResponseJson.Invalid -> {
clearPendingAccountDeletion()
DeleteAccountResult.Error(message = response.message, error = null)
DeleteAccountResult.Error(
message = response.validationErrors
?.values
?.firstOrNull()
?.firstOrNull(),
)
}
DeleteAccountResponseJson.Success -> {
@@ -521,10 +524,8 @@ class AuthRepositoryImpl(
)
override suspend fun createNewSsoUser(): NewSsoUserResult {
val account = authDiskSource.userState?.activeAccount
?: return NewSsoUserResult.Failure(error = NoActiveUserException())
val orgIdentifier = rememberedOrgIdentifier
?: return NewSsoUserResult.Failure(error = MissingPropertyException("OrgIdentifier"))
val account = authDiskSource.userState?.activeAccount ?: return NewSsoUserResult.Failure
val orgIdentifier = rememberedOrgIdentifier ?: return NewSsoUserResult.Failure
val userId = account.profile.userId
return organizationService
.getOrganizationAutoEnrollStatus(orgIdentifier)
@@ -576,7 +577,7 @@ class AuthRepositoryImpl(
}
.fold(
onSuccess = { NewSsoUserResult.Success },
onFailure = { NewSsoUserResult.Failure(error = it) },
onFailure = { NewSsoUserResult.Failure },
)
}
@@ -585,13 +586,10 @@ class AuthRepositoryImpl(
asymmetricalKey: String,
): LoginResult {
val profile = authDiskSource.userState?.activeAccount?.profile
?: return LoginResult.Error(errorMessage = null, error = NoActiveUserException())
?: return LoginResult.Error(errorMessage = null)
val userId = profile.userId
val privateKey = authDiskSource.getPrivateKey(userId = userId)
?: return LoginResult.Error(
errorMessage = null,
error = MissingPropertyException("Private Key"),
)
?: return LoginResult.Error(errorMessage = null)
checkForVaultUnlockError(
onVaultUnlockError = { error ->
@@ -641,7 +639,7 @@ class AuthRepositoryImpl(
onFailure = { throwable ->
when {
throwable.isSslHandShakeError() -> LoginResult.CertificateError
else -> LoginResult.Error(errorMessage = null, error = throwable)
else -> LoginResult.Error(errorMessage = null)
}
},
onSuccess = { it },
@@ -690,10 +688,7 @@ class AuthRepositoryImpl(
orgIdentifier = orgIdentifier,
)
}
?: LoginResult.Error(
errorMessage = null,
error = MissingPropertyException("Identity Token Auth Model"),
)
?: LoginResult.Error(errorMessage = null)
override suspend fun login(
email: String,
@@ -713,10 +708,7 @@ class AuthRepositoryImpl(
orgIdentifier = orgIdentifier,
)
}
?: LoginResult.Error(
errorMessage = null,
error = MissingPropertyException("Identity Token Auth Model"),
)
?: LoginResult.Error(errorMessage = null)
override suspend fun login(
email: String,
@@ -775,7 +767,7 @@ class AuthRepositoryImpl(
override suspend fun requestOneTimePasscode(): RequestOtpResult =
accountsService.requestOneTimePasscode()
.fold(
onFailure = { RequestOtpResult.Error(message = it.message, error = it) },
onFailure = { RequestOtpResult.Error(it.message) },
onSuccess = { RequestOtpResult.Success },
)
@@ -785,7 +777,7 @@ class AuthRepositoryImpl(
passcode = oneTimePasscode,
)
.fold(
onFailure = { VerifyOtpResult.NotVerified(errorMessage = it.message, error = it) },
onFailure = { VerifyOtpResult.NotVerified(it.message) },
onSuccess = { VerifyOtpResult.Verified },
)
@@ -793,27 +785,21 @@ class AuthRepositoryImpl(
resendEmailRequestJson
?.let { jsonRequest ->
accountsService.resendVerificationCodeEmail(body = jsonRequest).fold(
onFailure = { ResendEmailResult.Error(message = it.message, error = it) },
onFailure = { ResendEmailResult.Error(message = it.message) },
onSuccess = { ResendEmailResult.Success },
)
}
?: ResendEmailResult.Error(
message = null,
error = MissingPropertyException("Resend Email Request"),
)
?: ResendEmailResult.Error(message = null)
override suspend fun resendNewDeviceOtp(): ResendEmailResult =
resendNewDeviceOtpRequestJson
?.let { jsonRequest ->
accountsService.resendNewDeviceOtp(body = jsonRequest).fold(
onFailure = { ResendEmailResult.Error(message = it.message, error = it) },
onFailure = { ResendEmailResult.Error(message = it.message) },
onSuccess = { ResendEmailResult.Success },
)
}
?: ResendEmailResult.Error(
message = null,
error = MissingPropertyException("Resend New Device OTP Request"),
)
?: ResendEmailResult.Error(message = null)
override fun switchAccount(userId: String): SwitchAccountResult {
val currentUserState = authDiskSource.userState ?: return SwitchAccountResult.NoChange
@@ -915,10 +901,7 @@ class AuthRepositoryImpl(
is RegisterResponseJson.CaptchaRequired -> {
it.validationErrors.captchaKeys.firstOrNull()
?.let { key -> RegisterResult.CaptchaRequired(captchaId = key) }
?: RegisterResult.Error(
errorMessage = null,
error = MissingPropertyException("Captcha ID"),
)
?: RegisterResult.Error(errorMessage = null)
}
is RegisterResponseJson.Success -> {
@@ -927,11 +910,11 @@ class AuthRepositoryImpl(
}
is RegisterResponseJson.Invalid -> {
RegisterResult.Error(errorMessage = it.message, error = null)
RegisterResult.Error(errorMessage = it.message)
}
}
},
onFailure = { RegisterResult.Error(errorMessage = null, error = it) },
onFailure = { RegisterResult.Error(errorMessage = null) },
)
}
@@ -939,15 +922,11 @@ class AuthRepositoryImpl(
return accountsService.requestPasswordHint(email).fold(
onSuccess = {
when (it) {
is PasswordHintResponseJson.Error -> PasswordHintResult.Error(
message = it.errorMessage,
error = null,
)
is PasswordHintResponseJson.Error -> PasswordHintResult.Error(it.errorMessage)
PasswordHintResponseJson.Success -> PasswordHintResult.Success
}
},
onFailure = { PasswordHintResult.Error(message = null, error = it) },
onFailure = { PasswordHintResult.Error(null) },
)
}
@@ -955,12 +934,12 @@ class AuthRepositoryImpl(
val activeAccount = authDiskSource
.userState
?.activeAccount
?: return RemovePasswordResult.Error(error = NoActiveUserException())
?: return RemovePasswordResult.Error
val profile = activeAccount.profile
val userId = profile.userId
val userKey = authDiskSource
.getUserKey(userId = userId)
?: return RemovePasswordResult.Error(error = MissingPropertyException("User Key"))
?: return RemovePasswordResult.Error
val keyConnectorUrl = organizations
.find {
it.shouldUseKeyConnector &&
@@ -968,9 +947,7 @@ class AuthRepositoryImpl(
it.type != OrganizationType.ADMIN
}
?.keyConnectorUrl
?: return RemovePasswordResult.Error(
error = MissingPropertyException("Key Connector URL"),
)
?: return RemovePasswordResult.Error
return keyConnectorManager
.migrateExistingUserToKeyConnector(
userId = userId,
@@ -988,7 +965,7 @@ class AuthRepositoryImpl(
settingsRepository.setDefaultsIfNecessary(userId = userId)
}
.fold(
onFailure = { RemovePasswordResult.Error(error = it) },
onFailure = { RemovePasswordResult.Error },
onSuccess = { RemovePasswordResult.Success },
)
}
@@ -1001,7 +978,7 @@ class AuthRepositoryImpl(
val activeAccount = authDiskSource
.userState
?.activeAccount
?: return ResetPasswordResult.Error(error = NoActiveUserException())
?: return ResetPasswordResult.Error
val currentPasswordHash = currentPassword?.let { password ->
authSdkSource
.hashPassword(
@@ -1011,7 +988,7 @@ class AuthRepositoryImpl(
purpose = HashPurpose.SERVER_AUTHORIZATION,
)
.fold(
onFailure = { return ResetPasswordResult.Error(error = it) },
onFailure = { return ResetPasswordResult.Error },
onSuccess = { it },
)
}
@@ -1057,7 +1034,7 @@ class AuthRepositoryImpl(
// Return the success.
ResetPasswordResult.Success
},
onFailure = { ResetPasswordResult.Error(error = it) },
onFailure = { ResetPasswordResult.Error },
)
}
@@ -1070,7 +1047,7 @@ class AuthRepositoryImpl(
val activeAccount = authDiskSource
.userState
?.activeAccount
?: return SetPasswordResult.Error(error = NoActiveUserException())
?: return SetPasswordResult.Error
val userId = activeAccount.profile.userId
// Update the saved master password hash.
@@ -1081,7 +1058,7 @@ class AuthRepositoryImpl(
kdf = activeAccount.profile.toSdkParams(),
purpose = HashPurpose.SERVER_AUTHORIZATION,
)
.getOrElse { return@setPassword SetPasswordResult.Error(error = it) }
.getOrElse { return@setPassword SetPasswordResult.Error }
return when (activeAccount.profile.forcePasswordResetReason) {
ForcePasswordResetReason.TDE_USER_WITHOUT_PASSWORD_HAS_PASSWORD_RESET_PERMISSION -> {
@@ -1131,7 +1108,7 @@ class AuthRepositoryImpl(
}
}
.flatMap {
when (val result = vaultRepository.unlockVaultWithMasterPassword(password)) {
when (vaultRepository.unlockVaultWithMasterPassword(password)) {
is VaultUnlockResult.Success -> {
enrollUserInPasswordReset(
userId = userId,
@@ -1140,9 +1117,12 @@ class AuthRepositoryImpl(
)
}
is VaultUnlockError -> {
(result.error ?: IllegalStateException("Failed to unlock vault"))
.asFailure()
is VaultUnlockResult.AuthenticationError,
VaultUnlockResult.BiometricDecodingError,
VaultUnlockResult.InvalidStateError,
VaultUnlockResult.GenericError,
-> {
IllegalStateException("Failed to unlock vault").asFailure()
}
}
}
@@ -1152,7 +1132,7 @@ class AuthRepositoryImpl(
this.organizationIdentifier = null
}
.fold(
onFailure = { SetPasswordResult.Error(error = it) },
onFailure = { SetPasswordResult.Error },
onSuccess = { SetPasswordResult.Success },
)
}
@@ -1187,7 +1167,7 @@ class AuthRepositoryImpl(
verifiedDate = it.verifiedDate,
)
},
onFailure = { OrganizationDomainSsoDetailsResult.Failure(error = it) },
onFailure = { OrganizationDomainSsoDetailsResult.Failure },
)
override suspend fun getVerifiedOrganizationDomainSsoDetails(
@@ -1202,7 +1182,7 @@ class AuthRepositoryImpl(
verifiedOrganizationDomainSsoDetails = it.verifiedOrganizationDomainSsoDetails,
)
},
onFailure = { VerifiedOrganizationDomainSsoDetailsResult.Failure(error = it) },
onFailure = { VerifiedOrganizationDomainSsoDetailsResult.Failure },
)
override suspend fun prevalidateSso(
@@ -1215,19 +1195,19 @@ class AuthRepositoryImpl(
onSuccess = {
when (it) {
is PrevalidateSsoResponseJson.Error -> {
PrevalidateSsoResult.Failure(message = it.message, error = null)
PrevalidateSsoResult.Failure(message = it.message)
}
is PrevalidateSsoResponseJson.Success -> {
if (it.token.isNullOrBlank()) {
PrevalidateSsoResult.Failure(error = MissingPropertyException("Token"))
PrevalidateSsoResult.Failure()
} else {
PrevalidateSsoResult.Success(token = it.token)
}
}
}
},
onFailure = { PrevalidateSsoResult.Failure(error = it) },
onFailure = { PrevalidateSsoResult.Failure() },
)
override fun setSsoCallbackResult(result: SsoCallbackResult) {
@@ -1241,15 +1221,15 @@ class AuthRepositoryImpl(
deviceId = authDiskSource.uniqueAppId,
)
.fold(
onFailure = { KnownDeviceResult.Error(error = it) },
onSuccess = { KnownDeviceResult.Success(isKnownDevice = it) },
onFailure = { KnownDeviceResult.Error },
onSuccess = { KnownDeviceResult.Success(it) },
)
override suspend fun getPasswordBreachCount(password: String): BreachCountResult =
haveIBeenPwnedService
.getPasswordBreachCount(password)
.fold(
onFailure = { BreachCountResult.Error(error = it) },
onFailure = { BreachCountResult.Error },
onSuccess = { BreachCountResult.Success(it) },
)
@@ -1269,11 +1249,11 @@ class AuthRepositoryImpl(
)
.fold(
onSuccess = { PasswordStrengthResult.Success(passwordStrength = it) },
onFailure = { PasswordStrengthResult.Error(error = it) },
onFailure = { PasswordStrengthResult.Error },
)
override suspend fun validatePassword(password: String): ValidatePasswordResult {
val userId = activeUserId ?: return ValidatePasswordResult.Error(NoActiveUserException())
val userId = activeUserId ?: return ValidatePasswordResult.Error
return authDiskSource
.getMasterPasswordHash(userId = userId)
?.let { masterPasswordHash ->
@@ -1285,13 +1265,13 @@ class AuthRepositoryImpl(
)
.fold(
onSuccess = { ValidatePasswordResult.Success(isValid = it) },
onFailure = { ValidatePasswordResult.Error(error = it) },
onFailure = { ValidatePasswordResult.Error },
)
}
?: run {
val encryptedKey = authDiskSource
.getUserKey(userId)
?: return ValidatePasswordResult.Error(MissingPropertyException("UserKey"))
?: return ValidatePasswordResult.Error
vaultSdkSource
.validatePasswordUserKey(
userId = userId,
@@ -1321,12 +1301,10 @@ class AuthRepositoryImpl(
.userState
?.activeAccount
?.profile
?: return ValidatePinResult.Error(error = NoActiveUserException())
?: return ValidatePinResult.Error
val pinProtectedUserKey = authDiskSource
.getPinProtectedUserKey(userId = activeAccount.userId)
?: return ValidatePinResult.Error(
error = MissingPropertyException("Pin Protected User Key"),
)
?: return ValidatePinResult.Error
return vaultSdkSource
.validatePin(
userId = activeAccount.userId,
@@ -1335,7 +1313,7 @@ class AuthRepositoryImpl(
)
.fold(
onSuccess = { ValidatePinResult.Success(isValid = it) },
onFailure = { ValidatePinResult.Error(error = it) },
onFailure = { ValidatePinResult.Error },
)
}
@@ -1361,10 +1339,7 @@ class AuthRepositoryImpl(
onSuccess = {
when (it) {
is SendVerificationEmailResponseJson.Invalid -> {
SendVerificationEmailResult.Error(
errorMessage = it.message,
error = null,
)
SendVerificationEmailResult.Error(it.message)
}
is SendVerificationEmailResponseJson.Success -> {
@@ -1372,7 +1347,9 @@ class AuthRepositoryImpl(
}
}
},
onFailure = { SendVerificationEmailResult.Error(errorMessage = null, error = it) },
onFailure = {
SendVerificationEmailResult.Error(null)
},
)
override suspend fun validateEmailToken(email: String, token: String): EmailTokenResult {
@@ -1388,13 +1365,15 @@ class AuthRepositoryImpl(
when (val json = it) {
VerifyEmailTokenResponseJson.Valid -> EmailTokenResult.Success
is VerifyEmailTokenResponseJson.Invalid -> {
EmailTokenResult.Error(message = json.message, error = null)
EmailTokenResult.Error(json.message)
}
VerifyEmailTokenResponseJson.TokenExpired -> EmailTokenResult.Expired
}
},
onFailure = { EmailTokenResult.Error(message = null, error = it) },
onFailure = {
EmailTokenResult.Error(message = null)
},
)
}
@@ -1666,10 +1645,7 @@ class AuthRepositoryImpl(
LoginResult.UnofficialServerError
}
else -> LoginResult.Error(
errorMessage = null,
error = throwable,
)
else -> LoginResult.Error(errorMessage = null)
}
},
onSuccess = { loginResponse ->
@@ -1705,7 +1681,6 @@ class AuthRepositoryImpl(
is GetTokenResponseJson.Invalid.InvalidType.GenericInvalid -> {
LoginResult.Error(
errorMessage = loginResponse.errorMessage,
error = null,
)
}
}
@@ -1908,7 +1883,7 @@ class AuthRepositoryImpl(
}
.fold(
// If the request failed, we want to abort the login process
onFailure = { VaultUnlockResult.GenericError(error = it) },
onFailure = { VaultUnlockResult.GenericError },
onSuccess = { it },
)
} else {
@@ -1948,7 +1923,7 @@ class AuthRepositoryImpl(
}
.fold(
// If the request failed, we want to abort the login process
onFailure = { VaultUnlockResult.GenericError(error = it) },
onFailure = { VaultUnlockResult.GenericError },
onSuccess = { it },
)
}

View File

@@ -12,8 +12,5 @@ sealed class BreachCountResult {
/**
* There was an error determining if the password has been breached.
*/
data class Error(
val error: Throwable,
val message: String? = null,
) : BreachCountResult()
data object Error : BreachCountResult()
}

View File

@@ -12,8 +12,5 @@ sealed class DeleteAccountResult {
/**
* There was an error deleting the account.
*/
data class Error(
val message: String?,
val error: Throwable?,
) : DeleteAccountResult()
data class Error(val message: String?) : DeleteAccountResult()
}

View File

@@ -18,8 +18,5 @@ sealed class EmailTokenResult {
/**
* There was an error validating the token.
*/
data class Error(
val message: String?,
val error: Throwable?,
) : EmailTokenResult()
data class Error(val message: String?) : EmailTokenResult()
}

View File

@@ -12,7 +12,5 @@ sealed class KnownDeviceResult {
/**
* There was an error determining if this is a known device.
*/
data class Error(
val error: Throwable,
) : KnownDeviceResult()
data object Error : KnownDeviceResult()
}

View File

@@ -22,10 +22,7 @@ sealed class LoginResult {
/**
* There was an error logging in.
*/
data class Error(
val errorMessage: String?,
val error: Throwable?,
) : LoginResult()
data class Error(val errorMessage: String?) : LoginResult()
/**
* There was an error while logging into an unofficial Bitwarden server.

View File

@@ -8,12 +8,9 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
* the necessary `message` if applicable.
*/
fun VaultUnlockError.toLoginErrorResult(): LoginResult.Error = when (this) {
is VaultUnlockResult.AuthenticationError -> {
LoginResult.Error(errorMessage = this.message, error = this.error)
}
is VaultUnlockResult.BiometricDecodingError,
is VaultUnlockResult.GenericError,
is VaultUnlockResult.InvalidStateError,
-> LoginResult.Error(errorMessage = null, error = this.error)
is VaultUnlockResult.AuthenticationError -> LoginResult.Error(this.message)
VaultUnlockResult.BiometricDecodingError,
VaultUnlockResult.GenericError,
VaultUnlockResult.InvalidStateError,
-> LoginResult.Error(errorMessage = null)
}

View File

@@ -12,7 +12,5 @@ sealed class NewSsoUserResult {
/**
* There was an error while truing to create the new user.
*/
data class Failure(
val error: Throwable,
) : NewSsoUserResult()
data object Failure : NewSsoUserResult()
}

View File

@@ -22,7 +22,5 @@ sealed class OrganizationDomainSsoDetailsResult {
/**
* The request failed.
*/
data class Failure(
val error: Throwable,
) : OrganizationDomainSsoDetailsResult()
data object Failure : OrganizationDomainSsoDetailsResult()
}

View File

@@ -13,8 +13,5 @@ sealed class PasswordHintResult {
/**
* There was an error.
*/
data class Error(
val message: String?,
val error: Throwable?,
) : PasswordHintResult()
data class Error(val message: String?) : PasswordHintResult()
}

View File

@@ -16,7 +16,5 @@ sealed class PasswordStrengthResult {
/**
* There was an error determining the password strength.
*/
data class Error(
val error: Throwable,
) : PasswordStrengthResult()
data object Error : PasswordStrengthResult()
}

View File

@@ -16,6 +16,5 @@ sealed class PrevalidateSsoResult {
*/
data class Failure(
val message: String? = null,
val error: Throwable?,
) : PrevalidateSsoResult()
}

View File

@@ -23,10 +23,7 @@ sealed class RegisterResult {
*
* @param errorMessage a message describing the error.
*/
data class Error(
val errorMessage: String?,
val error: Throwable?,
) : RegisterResult()
data class Error(val errorMessage: String?) : RegisterResult()
/**
* Password hash was found in a data breach.

View File

@@ -12,7 +12,5 @@ sealed class RemovePasswordResult {
/**
* There was an error removing the password.
*/
data class Error(
val error: Throwable,
) : RemovePasswordResult()
data object Error : RemovePasswordResult()
}

View File

@@ -13,8 +13,5 @@ sealed class RequestOtpResult {
/**
* Represents a failure to send the one-time passcode.
*/
data class Error(
val message: String?,
val error: Throwable,
) : RequestOtpResult()
data class Error(val message: String?) : RequestOtpResult()
}

View File

@@ -13,8 +13,5 @@ sealed class ResendEmailResult {
/**
* There was an error.
*/
data class Error(
val message: String?,
val error: Throwable,
) : ResendEmailResult()
data class Error(val message: String?) : ResendEmailResult()
}

View File

@@ -12,7 +12,5 @@ sealed class ResetPasswordResult {
/**
* There was an error resetting the password.
*/
data class Error(
val error: Throwable,
) : ResetPasswordResult()
data object Error : ResetPasswordResult()
}

View File

@@ -18,8 +18,5 @@ sealed class SendVerificationEmailResult {
*
* @param errorMessage a message describing the error.
*/
data class Error(
val errorMessage: String?,
val error: Throwable?,
) : SendVerificationEmailResult()
data class Error(val errorMessage: String?) : SendVerificationEmailResult()
}

View File

@@ -12,7 +12,5 @@ sealed class SetPasswordResult {
/**
* There was an error setting the password.
*/
data class Error(
val error: Throwable,
) : SetPasswordResult()
data object Error : SetPasswordResult()
}

View File

@@ -14,7 +14,5 @@ sealed class UserFingerprintResult {
/**
* There was an error getting the user fingerprint.
*/
data class Error(
val error: Throwable,
) : UserFingerprintResult()
data object Error : UserFingerprintResult()
}

View File

@@ -15,7 +15,5 @@ sealed class ValidatePasswordResult {
/**
* There was an error determining if the validity of the password.
*/
data class Error(
val error: Throwable,
) : ValidatePasswordResult()
data object Error : ValidatePasswordResult()
}

View File

@@ -14,7 +14,5 @@ sealed class ValidatePinResult {
/**
* There was an error determining if the validity of the PIN.
*/
data class Error(
val error: Throwable,
) : ValidatePinResult()
data object Error : ValidatePinResult()
}

View File

@@ -18,7 +18,5 @@ sealed class VerifiedOrganizationDomainSsoDetailsResult {
/**
* The request failed.
*/
data class Failure(
val error: Throwable,
) : VerifiedOrganizationDomainSsoDetailsResult()
data object Failure : VerifiedOrganizationDomainSsoDetailsResult()
}

View File

@@ -13,8 +13,5 @@ sealed class VerifyOtpResult {
/**
* Represents a failure to verify the one-time passcode.
*/
data class NotVerified(
val errorMessage: String?,
val error: Throwable,
) : VerifyOtpResult()
data class NotVerified(val errorMessage: String?) : VerifyOtpResult()
}

View File

@@ -32,11 +32,6 @@ class BitwardenAccessibilityService : AccessibilityService() {
override fun onInterrupt() = Unit
override fun onCreate() {
super.onCreate()
accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings()
}
override fun onUnbind(intent: Intent?): Boolean {
return super
.onUnbind(intent)

View File

@@ -3,9 +3,7 @@ package com.x8bit.bitwarden.data.autofill.accessibility.util
import android.content.Context
import android.provider.Settings
import com.x8bit.bitwarden.LEGACY_ACCESSIBILITY_SERVICE_NAME
import com.x8bit.bitwarden.LEGACY_SHORT_ACCESSIBILITY_SERVICE_NAME
import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService
import com.x8bit.bitwarden.data.autofill.util.containsAnyTerms
/**
* Helper method to determine if the [BitwardenAccessibilityService] is enabled.
@@ -13,25 +11,16 @@ import com.x8bit.bitwarden.data.autofill.util.containsAnyTerms
val Context.isAccessibilityServiceEnabled: Boolean
get() {
val appContext = this.applicationContext
val packageName = appContext.packageName
val accessibilityServiceName = packageName?.let {
"$it/$LEGACY_ACCESSIBILITY_SERVICE_NAME"
}
val shortAccessibilityServiceName = packageName.let {
"$it/$LEGACY_SHORT_ACCESSIBILITY_SERVICE_NAME"
}
val accessibilityServiceName = appContext
.packageName
?.let { "$it/$LEGACY_ACCESSIBILITY_SERVICE_NAME" }
?: return false
return Settings
.Secure
.getString(
appContext.contentResolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
)
?.containsAnyTerms(
terms = listOfNotNull(
accessibilityServiceName,
shortAccessibilityServiceName,
),
ignoreCase = true,
)
?.contains(accessibilityServiceName)
?: false
}

View File

@@ -267,7 +267,7 @@ class Fido2ProviderProcessorImpl(
val result = vaultRepository
.getDecryptedFido2CredentialAutofillViews(cipherViews)
return when (result) {
is DecryptFido2CredentialAutofillViewResult.Error -> {
DecryptFido2CredentialAutofillViewResult.Error -> {
throw GetCredentialUnknownException("Error decrypting credentials.")
}

View File

@@ -1,8 +0,0 @@
package com.x8bit.bitwarden.data.platform.error
/**
* An exception indicating that a required property was missing.
*/
class MissingPropertyException(
propertyName: String,
) : IllegalStateException("Missing the required $propertyName property")

View File

@@ -1,6 +0,0 @@
package com.x8bit.bitwarden.data.platform.error
/**
* An exception indicating that there is currently no active user when one is required.
*/
class NoActiveUserException : IllegalStateException("No current active user!")

View File

@@ -158,7 +158,7 @@ class FirstTimeActionManagerImpl @Inject constructor(
override val shouldShowAddLoginCoachMarkFlow: Flow<Boolean>
get() = settingsDiskSource
.getShouldShowAddLoginCoachMarkFlow()
.map { it != false }
.map { it ?: true }
.mapFalseIfAnyLoginCiphersAvailable()
.combine(
featureFlagManager.getFeatureFlagFlow(FlagKey.OnboardingFlow),
@@ -172,13 +172,11 @@ class FirstTimeActionManagerImpl @Inject constructor(
override val shouldShowGeneratorCoachMarkFlow: Flow<Boolean>
get() = settingsDiskSource
.getShouldShowGeneratorCoachMarkFlow()
.map { it != false }
.map { it ?: true }
.mapFalseIfAnyLoginCiphersAvailable()
.combine(
featureFlagManager.getFeatureFlagFlow(FlagKey.OnboardingFlow),
) { shouldShow, featureFlagEnabled ->
// If the feature flag is off always return true so observers know
// the card has not been shown.
shouldShow && featureFlagEnabled
}
.distinctUntilChanged()
@@ -300,8 +298,8 @@ class FirstTimeActionManagerImpl @Inject constructor(
}
/**
* If there are any existing "Login" type ciphers then we'll map the current value
* of the receiver Flow to `false`.
* If there are any existing "Login" type ciphers then we'll map the current value
* of the receiver Flow to `false`.
*/
@OptIn(ExperimentalCoroutinesApi::class)
private fun Flow<Boolean>.mapFalseIfAnyLoginCiphersAvailable(): Flow<Boolean> =
@@ -312,10 +310,8 @@ class FirstTimeActionManagerImpl @Inject constructor(
combine(
flow = this,
flow2 = vaultDiskSource.getCiphers(activeUserId),
) { receiverCurrentValue, ciphers ->
receiverCurrentValue && ciphers.none {
it.login != null && it.organizationId == null
}
) { currentValue, ciphers ->
currentValue && ciphers.none { it.login != null }
}
}
.distinctUntilChanged()

View File

@@ -5,7 +5,6 @@ import android.security.KeyChain
import android.security.KeyChainException
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsCertificate
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
import com.x8bit.bitwarden.data.platform.manager.model.ImportPrivateKeyResult
import timber.log.Timber
import java.io.IOException
@@ -25,7 +24,7 @@ class KeyManagerImpl(
private val context: Context,
) : KeyManager {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Suppress("CyclomaticComplexMethod")
override fun importMutualTlsCertificate(
key: ByteArray,
alias: String,
@@ -36,29 +35,28 @@ class KeyManagerImpl(
.inputStream()
.use { stream ->
try {
KeyStore
.getInstance(KEYSTORE_TYPE_PKCS12)
KeyStore.getInstance(KEYSTORE_TYPE_PKCS12)
.also { it.load(stream, password.toCharArray()) }
} catch (e: KeyStoreException) {
Timber.Forest.e(e, "Failed to load PKCS12 bytes")
return ImportPrivateKeyResult.Error.UnsupportedKey(throwable = e)
return ImportPrivateKeyResult.Error.UnsupportedKey
} catch (e: IOException) {
Timber.Forest.e(e, "Format or password error while loading PKCS12 bytes")
return when (e.cause) {
is UnrecoverableKeyException -> {
ImportPrivateKeyResult.Error.UnrecoverableKey(throwable = e)
ImportPrivateKeyResult.Error.UnrecoverableKey
}
else -> {
ImportPrivateKeyResult.Error.KeyStoreOperationFailed(throwable = e)
ImportPrivateKeyResult.Error.KeyStoreOperationFailed
}
}
} catch (e: CertificateException) {
Timber.Forest.e(e, "Unable to load certificate chain")
return ImportPrivateKeyResult.Error.InvalidCertificateChain(throwable = e)
return ImportPrivateKeyResult.Error.InvalidCertificateChain
} catch (e: NoSuchAlgorithmException) {
Timber.Forest.e(e, "Cryptographic algorithm not supported")
return ImportPrivateKeyResult.Error.UnsupportedKey(throwable = e)
return ImportPrivateKeyResult.Error.UnsupportedKey
}
}
@@ -66,29 +64,22 @@ class KeyManagerImpl(
val internalAlias = pkcs12KeyStore.aliases()
?.takeIf { it.hasMoreElements() }
?.nextElement()
?: return ImportPrivateKeyResult.Error.UnsupportedKey(
throwable = MissingPropertyException("Internal Alias"),
)
?: return ImportPrivateKeyResult.Error.UnsupportedKey
// Step 3: Extract PrivateKey and X.509 certificate from the KeyStore and verify
// certificate alias.
val privateKey = try {
pkcs12KeyStore
.getKey(internalAlias, password.toCharArray())
?: return ImportPrivateKeyResult.Error.UnrecoverableKey(
throwable = MissingPropertyException("Private Key"),
)
pkcs12KeyStore.getKey(internalAlias, password.toCharArray())
?: return ImportPrivateKeyResult.Error.UnrecoverableKey
} catch (e: UnrecoverableKeyException) {
Timber.Forest.e(e, "Failed to get private key")
return ImportPrivateKeyResult.Error.UnrecoverableKey(throwable = e)
return ImportPrivateKeyResult.Error.UnrecoverableKey
}
val certChain: Array<Certificate> = pkcs12KeyStore
.getCertificateChain(internalAlias)
?.takeUnless { it.isEmpty() }
?: return ImportPrivateKeyResult.Error.InvalidCertificateChain(
throwable = MissingPropertyException("Certificate Chain"),
)
?: return ImportPrivateKeyResult.Error.InvalidCertificateChain
// Step 4: Store the private key and X.509 certificate in the AndroidKeyStore if the alias
// does not exists.
@@ -101,7 +92,7 @@ class KeyManagerImpl(
setKeyEntry(alias, privateKey, null, certChain)
} catch (e: KeyStoreException) {
Timber.Forest.e(e, "Failed to import key into Android KeyStore")
return ImportPrivateKeyResult.Error.KeyStoreOperationFailed(throwable = e)
return ImportPrivateKeyResult.Error.KeyStoreOperationFailed
}
}
return ImportPrivateKeyResult.Success(alias)

View File

@@ -44,13 +44,12 @@ sealed class FlagKey<out T : Any> {
AnonAddySelfHostAlias,
SimpleLoginSelfHostAlias,
ChromeAutofill,
MobileErrorReporting,
)
}
}
/**
* Data object holding the key for syncing with the Bitwarden Authenticator app.
* Data object holding the key for syncing with the Bitwarden Authenticator app.
*/
data object AuthenticatorSync : FlagKey<Boolean>() {
override val keyName: String = "enable-pm-bwa-sync"
@@ -67,15 +66,6 @@ sealed class FlagKey<out T : Any> {
override val isRemotelyConfigured: Boolean = true
}
/**
* Data object holding the key for syncing with the Bitwarden Authenticator app.
*/
data object MobileErrorReporting : FlagKey<Boolean>() {
override val keyName: String = "mobile-error-reporting"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = false
}
/**
* Data object holding the feature flag key for the Onboarding Carousel feature.
*/
@@ -228,7 +218,7 @@ sealed class FlagKey<out T : Any> {
* autofill.
*/
data object ChromeAutofill : FlagKey<Boolean>() {
override val keyName: String = "android-chrome-autofill"
override val keyName: String = "enable-pm-chrome-autofill"
override val defaultValue: Boolean = false
override val isRemotelyConfigured: Boolean = true
}

View File

@@ -16,44 +16,30 @@ sealed class ImportPrivateKeyResult {
* Represents a generic error during the import process.
*/
sealed class Error : ImportPrivateKeyResult() {
/**
* The underlying error.
*/
abstract val throwable: Throwable?
/**
* Indicates that the provided key is unrecoverable or the password is incorrect.
*/
data class UnrecoverableKey(
override val throwable: Throwable,
) : Error()
data object UnrecoverableKey : Error()
/**
* Indicates that the certificate chain associated with the key is invalid.
*/
data class InvalidCertificateChain(
override val throwable: Throwable,
) : Error()
data object InvalidCertificateChain : Error()
/**
* Indicates that the specified alias is already in use.
*/
data object DuplicateAlias : Error() {
override val throwable: Throwable? = null
}
data object DuplicateAlias : Error()
/**
* Indicates that an error occurred during the key store operation.
*/
data class KeyStoreOperationFailed(
override val throwable: Throwable,
) : Error()
data object KeyStoreOperationFailed : Error()
/**
* Indicates the provided key is not supported.
*/
data class UnsupportedKey(
override val throwable: Throwable,
) : Error()
data object UnsupportedKey : Error()
}
}

View File

@@ -2,9 +2,8 @@ package com.x8bit.bitwarden.data.platform.manager.model
import com.x8bit.bitwarden.data.platform.manager.PushManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
import java.time.ZonedDateTime
/**
@@ -13,7 +12,6 @@ import java.time.ZonedDateTime
* Note: The data we receive is not always reliable, so everything is nullable and we validate the
* data in the [PushManager] as necessary.
*/
@OptIn(ExperimentalSerializationApi::class)
@Serializable
sealed class NotificationPayload {
/**
@@ -26,12 +24,12 @@ sealed class NotificationPayload {
*/
@Serializable
data class SyncCipherNotification(
@JsonNames("Id", "id") val cipherId: String?,
@JsonNames("UserId", "userId") override val userId: String?,
@JsonNames("OrganizationId", "organizationId") val organizationId: String?,
@JsonNames("CollectionIds", "collectionIds") val collectionIds: List<String>?,
@SerialName("Id") val cipherId: String?,
@SerialName("UserId") override val userId: String?,
@SerialName("OrganizationId") val organizationId: String?,
@SerialName("CollectionIds") val collectionIds: List<String>?,
@Contextual
@JsonNames("RevisionDate", "revisionDate") val revisionDate: ZonedDateTime?,
@SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
) : NotificationPayload()
/**
@@ -39,10 +37,10 @@ sealed class NotificationPayload {
*/
@Serializable
data class SyncFolderNotification(
@JsonNames("Id", "id") val folderId: String?,
@JsonNames("UserId", "userId") override val userId: String?,
@SerialName("Id") val folderId: String?,
@SerialName("UserId") override val userId: String?,
@Contextual
@JsonNames("RevisionDate", "revisionDate") val revisionDate: ZonedDateTime?,
@SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
) : NotificationPayload()
/**
@@ -50,9 +48,9 @@ sealed class NotificationPayload {
*/
@Serializable
data class UserNotification(
@JsonNames("UserId", "userId") override val userId: String?,
@SerialName("UserId") override val userId: String?,
@Contextual
@JsonNames("Date", "date") val date: ZonedDateTime?,
@SerialName("Date") val date: ZonedDateTime?,
) : NotificationPayload()
/**
@@ -60,10 +58,10 @@ sealed class NotificationPayload {
*/
@Serializable
data class SyncSendNotification(
@JsonNames("Id", "id") val sendId: String?,
@JsonNames("UserId", "userId") override val userId: String?,
@SerialName("Id") val sendId: String?,
@SerialName("UserId") override val userId: String?,
@Contextual
@JsonNames("RevisionDate", "revisionDate") val revisionDate: ZonedDateTime?,
@SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
) : NotificationPayload()
/**
@@ -71,7 +69,7 @@ sealed class NotificationPayload {
*/
@Serializable
data class PasswordlessRequestNotification(
@JsonNames("UserId", "userId") override val userId: String?,
@JsonNames("Id", "id") val loginRequestId: String?,
@SerialName("UserId") override val userId: String?,
@SerialName("Id") val loginRequestId: String?,
) : NotificationPayload()
}

View File

@@ -72,9 +72,9 @@ class AuthenticatorBridgeRepositoryImpl(
when (unlockResult) {
is VaultUnlockResult.AuthenticationError,
is VaultUnlockResult.BiometricDecodingError,
is VaultUnlockResult.GenericError,
is VaultUnlockResult.InvalidStateError,
VaultUnlockResult.BiometricDecodingError,
VaultUnlockResult.GenericError,
VaultUnlockResult.InvalidStateError,
-> {
// Not being able to unlock the user's vault with the
// decrypted unlock key is an unexpected case, but if it does

View File

@@ -10,7 +10,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.BiometricsKeyResult
@@ -381,13 +380,12 @@ class SettingsRepositoryImpl(
}
override suspend fun getUserFingerprint(): UserFingerprintResult {
val userId = activeUserId
?: return UserFingerprintResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return UserFingerprintResult.Error
return vaultSdkSource
.getUserFingerprint(userId)
.fold(
onFailure = { UserFingerprintResult.Error(error = it) },
onFailure = { UserFingerprintResult.Error },
onSuccess = { UserFingerprintResult.Success(it) },
)
}
@@ -494,8 +492,7 @@ class SettingsRepositoryImpl(
}
override suspend fun setupBiometricsKey(cipher: Cipher): BiometricsKeyResult {
val userId = activeUserId
?: return BiometricsKeyResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return BiometricsKeyResult.Error
return vaultSdkSource
.getUserEncryptionKey(userId = userId)
.onSuccess { biometricsKey ->
@@ -509,7 +506,7 @@ class SettingsRepositoryImpl(
}
.fold(
onSuccess = { BiometricsKeyResult.Success },
onFailure = { BiometricsKeyResult.Error(error = it) },
onFailure = { BiometricsKeyResult.Error },
)
}

View File

@@ -12,7 +12,5 @@ sealed class BiometricsKeyResult {
/**
* Generic error while setting up the biometrics key.
*/
data class Error(
val error: Throwable,
) : BiometricsKeyResult()
data object Error : BiometricsKeyResult()
}

View File

@@ -1,6 +1,5 @@
package com.x8bit.bitwarden.data.platform.util
import android.os.Build
import com.x8bit.bitwarden.BuildConfig
/**
@@ -8,55 +7,3 @@ import com.x8bit.bitwarden.BuildConfig
*/
val isFdroid: Boolean
get() = BuildConfig.FLAVOR == "fdroid"
/**
* A string that represents a displayable app version.
*/
val versionData: String
get() = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
/**
* A string that represents device data.
*/
val deviceData: String get() = "$deviceBrandModel $osInfo $buildInfo"
/**
* A string representing the CI information if available.
*/
val ciBuildInfo: String? get() = BuildConfig.CI_INFO.takeUnless { it.isBlank() }
/**
* A string representing the build flavor or blank if it is the standard configuration.
*/
private val buildFlavorName: String
get() = when (BuildConfig.FLAVOR) {
"standard" -> ""
else -> "-${BuildConfig.FLAVOR}"
}
/**
* A string representing the build type.
*/
private val buildTypeName: String
get() = when (BuildConfig.BUILD_TYPE) {
"debug" -> "dev"
"release" -> "prod"
else -> BuildConfig.BUILD_TYPE
}
/**
* A string representing the device brand and model.
*/
private val deviceBrandModel: String get() = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}"
/**
* A string representing the operating system information.
*/
private val osInfo: String get() = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}"
/**
* A string representing the build information.
*/
private val buildInfo: String
get() = "\uD83D\uDCE6 $buildTypeName" +
buildFlavorName.takeUnless { it.isBlank() }?.let { " $it" }.orEmpty()

View File

@@ -130,7 +130,7 @@ class GeneratorRepositoryImpl(
}
GeneratedPasswordResult.Success(generatedPassword)
},
onFailure = { GeneratedPasswordResult.InvalidRequest(error = it) },
onFailure = { GeneratedPasswordResult.InvalidRequest },
)
override suspend fun generatePassphrase(
@@ -149,7 +149,7 @@ class GeneratorRepositoryImpl(
}
GeneratedPassphraseResult.Success(generatedPassphrase)
},
onFailure = { GeneratedPassphraseResult.InvalidRequest(error = it) },
onFailure = { GeneratedPassphraseResult.InvalidRequest },
)
override suspend fun generatePlusAddressedEmail(
@@ -161,7 +161,7 @@ class GeneratorRepositoryImpl(
GeneratedPlusAddressedUsernameResult.Success(generatedEmail)
},
onFailure = {
GeneratedPlusAddressedUsernameResult.InvalidRequest(error = it)
GeneratedPlusAddressedUsernameResult.InvalidRequest
},
)
@@ -174,7 +174,7 @@ class GeneratorRepositoryImpl(
GeneratedCatchAllUsernameResult.Success(generatedEmail)
},
onFailure = {
GeneratedCatchAllUsernameResult.InvalidRequest(error = it)
GeneratedCatchAllUsernameResult.InvalidRequest
},
)
@@ -187,7 +187,7 @@ class GeneratorRepositoryImpl(
GeneratedRandomWordUsernameResult.Success(generatedUsername)
},
onFailure = {
GeneratedRandomWordUsernameResult.InvalidRequest(error = it)
GeneratedRandomWordUsernameResult.InvalidRequest
},
)
@@ -200,7 +200,7 @@ class GeneratorRepositoryImpl(
GeneratedForwardedServiceUsernameResult.Success(generatedEmail)
},
onFailure = {
GeneratedForwardedServiceUsernameResult.InvalidRequest(it.message, error = it)
GeneratedForwardedServiceUsernameResult.InvalidRequest(it.message)
},
)
}

View File

@@ -15,5 +15,5 @@ sealed class GeneratedCatchAllUsernameResult {
/**
* There was an error during the operation.
*/
data class InvalidRequest(val error: Throwable) : GeneratedCatchAllUsernameResult()
data object InvalidRequest : GeneratedCatchAllUsernameResult()
}

View File

@@ -14,8 +14,5 @@ sealed class GeneratedForwardedServiceUsernameResult {
/**
* There was an error during the operation.
*/
data class InvalidRequest(
val message: String?,
val error: Throwable,
) : GeneratedForwardedServiceUsernameResult()
data class InvalidRequest(val message: String?) : GeneratedForwardedServiceUsernameResult()
}

View File

@@ -12,5 +12,5 @@ sealed class GeneratedPassphraseResult {
/**
* There was an error during the operation.
*/
data class InvalidRequest(val error: Throwable) : GeneratedPassphraseResult()
data object InvalidRequest : GeneratedPassphraseResult()
}

View File

@@ -13,5 +13,5 @@ sealed class GeneratedPasswordResult {
/**
* There was an error during the operation.
*/
data class InvalidRequest(val error: Throwable) : GeneratedPasswordResult()
data object InvalidRequest : GeneratedPasswordResult()
}

View File

@@ -14,5 +14,5 @@ sealed class GeneratedPlusAddressedUsernameResult {
/**
* There was an error during the operation.
*/
data class InvalidRequest(val error: Throwable) : GeneratedPlusAddressedUsernameResult()
data object InvalidRequest : GeneratedPlusAddressedUsernameResult()
}

View File

@@ -15,5 +15,5 @@ sealed class GeneratedRandomWordUsernameResult {
/**
* There was an error during the operation.
*/
data class InvalidRequest(val error: Throwable) : GeneratedRandomWordUsernameResult()
data object InvalidRequest : GeneratedRandomWordUsernameResult()
}

View File

@@ -169,10 +169,7 @@ class VaultSdkSourceImpl(
InitializeCryptoResult.Success
} catch (exception: BitwardenException) {
// The only truly expected error from the SDK is an incorrect key/password.
InitializeCryptoResult.AuthenticationError(
message = exception.message,
error = exception,
)
InitializeCryptoResult.AuthenticationError(message = exception.message)
}
}
@@ -190,7 +187,6 @@ class VaultSdkSourceImpl(
// The only truly expected error from the SDK is for incorrect keys.
InitializeCryptoResult.AuthenticationError(
message = exception.message,
error = exception,
)
}
}

View File

@@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk.model
import com.bitwarden.vault.CipherView
/**
* Models the result of querying for ciphers with FIDO 2 credentials.
*/
sealed class FindFido2CredentialsResult {
/**
* Indicates the query was executed successfully.
*/
data class Success(val cipherViews: List<CipherView>) : FindFido2CredentialsResult()
/**
* Indicates the query was not executed successfully.
*/
data object Error : FindFido2CredentialsResult()
}

View File

@@ -15,6 +15,5 @@ sealed class InitializeCryptoResult {
*/
data class AuthenticationError(
val message: String? = null,
val error: Throwable,
) : InitializeCryptoResult()
}

View File

@@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk.model
/**
* Models the result of saving a FIDO 2 credential.
*/
sealed class SaveCredentialResult {
/**
* Indicates the credential has been saved.
*/
data object Success : SaveCredentialResult()
/**
* Indicates the credential was not saved.
*/
data object Error : SaveCredentialResult()
}

View File

@@ -6,7 +6,6 @@ import com.bitwarden.vault.AttachmentView
import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
@@ -50,8 +49,7 @@ class CipherManagerImpl(
private val activeUserId: String? get() = authDiskSource.userState?.activeUserId
override suspend fun createCipher(cipherView: CipherView): CreateCipherResult {
val userId = activeUserId
?: return CreateCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return CreateCipherResult.Error
return vaultSdkSource
.encryptCipher(
userId = userId,
@@ -60,7 +58,7 @@ class CipherManagerImpl(
.flatMap { ciphersService.createCipher(body = it.toEncryptedNetworkCipher()) }
.onSuccess { vaultDiskSource.saveCipher(userId = userId, cipher = it) }
.fold(
onFailure = { CreateCipherResult.Error(error = it) },
onFailure = { CreateCipherResult.Error },
onSuccess = {
reviewPromptManager.registerAddCipherAction()
CreateCipherResult.Success
@@ -72,8 +70,7 @@ class CipherManagerImpl(
cipherView: CipherView,
collectionIds: List<String>,
): CreateCipherResult {
val userId = activeUserId
?: return CreateCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return CreateCipherResult.Error
return vaultSdkSource
.encryptCipher(
userId = userId,
@@ -94,7 +91,7 @@ class CipherManagerImpl(
)
}
.fold(
onFailure = { CreateCipherResult.Error(error = it) },
onFailure = { CreateCipherResult.Error },
onSuccess = {
reviewPromptManager.registerAddCipherAction()
CreateCipherResult.Success
@@ -103,14 +100,13 @@ class CipherManagerImpl(
}
override suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult {
val userId = activeUserId
?: return DeleteCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return DeleteCipherResult.Error
return ciphersService
.hardDeleteCipher(cipherId = cipherId)
.onSuccess { vaultDiskSource.deleteCipher(userId = userId, cipherId = cipherId) }
.fold(
onSuccess = { DeleteCipherResult.Success },
onFailure = { DeleteCipherResult.Error(error = it) },
onFailure = { DeleteCipherResult.Error },
)
}
@@ -118,8 +114,7 @@ class CipherManagerImpl(
cipherId: String,
cipherView: CipherView,
): DeleteCipherResult {
val userId = activeUserId
?: return DeleteCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return DeleteCipherResult.Error
return cipherView
.encryptCipherAndCheckForMigration(userId = userId, cipherId = cipherId)
.flatMap { cipher ->
@@ -141,7 +136,7 @@ class CipherManagerImpl(
}
.fold(
onSuccess = { DeleteCipherResult.Success },
onFailure = { DeleteCipherResult.Error(error = it) },
onFailure = { DeleteCipherResult.Error },
)
}
@@ -157,7 +152,7 @@ class CipherManagerImpl(
)
.fold(
onSuccess = { DeleteAttachmentResult.Success },
onFailure = { DeleteAttachmentResult.Error(error = it) },
onFailure = { DeleteAttachmentResult.Error },
)
private suspend fun deleteCipherAttachmentForResult(
@@ -165,7 +160,7 @@ class CipherManagerImpl(
attachmentId: String,
cipherView: CipherView,
): Result<Cipher> {
val userId = activeUserId ?: return NoActiveUserException().asFailure()
val userId = activeUserId ?: return IllegalStateException("No active user").asFailure()
return ciphersService
.deleteCipherAttachment(
cipherId = cipherId,
@@ -192,8 +187,7 @@ class CipherManagerImpl(
cipherId: String,
cipherView: CipherView,
): RestoreCipherResult {
val userId = activeUserId
?: return RestoreCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return RestoreCipherResult.Error
return ciphersService
.restoreCipher(cipherId = cipherId)
.onSuccess {
@@ -204,7 +198,7 @@ class CipherManagerImpl(
}
.fold(
onSuccess = { RestoreCipherResult.Success },
onFailure = { RestoreCipherResult.Error(error = it) },
onFailure = { RestoreCipherResult.Error },
)
}
@@ -212,8 +206,7 @@ class CipherManagerImpl(
cipherId: String,
cipherView: CipherView,
): UpdateCipherResult {
val userId = activeUserId
?: return UpdateCipherResult.Error(errorMessage = null, error = NoActiveUserException())
val userId = activeUserId ?: return UpdateCipherResult.Error(errorMessage = null)
return vaultSdkSource
.encryptCipher(
userId = userId,
@@ -228,7 +221,7 @@ class CipherManagerImpl(
.map { response ->
when (response) {
is UpdateCipherResponseJson.Invalid -> {
UpdateCipherResult.Error(errorMessage = response.message, error = null)
UpdateCipherResult.Error(errorMessage = response.message)
}
is UpdateCipherResponseJson.Success -> {
@@ -241,7 +234,7 @@ class CipherManagerImpl(
}
}
.fold(
onFailure = { UpdateCipherResult.Error(errorMessage = null, error = it) },
onFailure = { UpdateCipherResult.Error(errorMessage = null) },
onSuccess = { it },
)
}
@@ -252,7 +245,7 @@ class CipherManagerImpl(
cipherView: CipherView,
collectionIds: List<String>,
): ShareCipherResult {
val userId = activeUserId ?: return ShareCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return ShareCipherResult.Error
return migrateAttachments(userId = userId, cipherView = cipherView)
.flatMap {
vaultSdkSource.moveToOrganization(
@@ -278,7 +271,7 @@ class CipherManagerImpl(
)
}
.fold(
onFailure = { ShareCipherResult.Error(error = it) },
onFailure = { ShareCipherResult.Error },
onSuccess = { ShareCipherResult.Success },
)
}
@@ -288,7 +281,7 @@ class CipherManagerImpl(
cipherView: CipherView,
collectionIds: List<String>,
): ShareCipherResult {
val userId = activeUserId ?: return ShareCipherResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return ShareCipherResult.Error
return ciphersService
.updateCipherCollections(
cipherId = cipherId,
@@ -308,7 +301,7 @@ class CipherManagerImpl(
}
.fold(
onSuccess = { ShareCipherResult.Success },
onFailure = { ShareCipherResult.Error(error = it) },
onFailure = { ShareCipherResult.Error },
)
}
@@ -327,7 +320,7 @@ class CipherManagerImpl(
fileUri = fileUri,
)
.fold(
onFailure = { CreateAttachmentResult.Error(error = it) },
onFailure = { CreateAttachmentResult.Error },
onSuccess = { CreateAttachmentResult.Success(cipherView = it) },
)
@@ -339,7 +332,7 @@ class CipherManagerImpl(
fileName: String?,
fileUri: Uri,
): Result<CipherView> {
val userId = activeUserId ?: return NoActiveUserException().asFailure()
val userId = activeUserId ?: return IllegalStateException("No active user").asFailure()
val attachmentView = AttachmentView(
id = null,
url = null,
@@ -411,14 +404,14 @@ class CipherManagerImpl(
)
.fold(
onSuccess = { DownloadAttachmentResult.Success(file = it) },
onFailure = { DownloadAttachmentResult.Failure(error = it) },
onFailure = { DownloadAttachmentResult.Failure },
)
private suspend fun downloadAttachmentForResult(
cipherView: CipherView,
attachmentId: String,
): Result<File> {
val userId = activeUserId ?: return NoActiveUserException().asFailure()
val userId = activeUserId ?: return IllegalStateException("No active user").asFailure()
val cipher = cipherView
.encryptCipherAndCheckForMigration(
@@ -446,10 +439,7 @@ class CipherManagerImpl(
?: return IllegalStateException("Attachment does not have a url").asFailure()
val encryptedFile = when (val result = fileManager.downloadFileToCache(url)) {
is DownloadResult.Failure -> {
return IllegalStateException("Download failed", result.error).asFailure()
}
DownloadResult.Failure -> return IllegalStateException("Download failed").asFailure()
is DownloadResult.Success -> result.file
}

View File

@@ -44,7 +44,7 @@ class FileManagerImpl(
.getDataStream(url)
.fold(
onSuccess = { it },
onFailure = { return DownloadResult.Failure(error = it) },
onFailure = { return DownloadResult.Failure },
)
// Create a temporary file in cache to write to
@@ -66,7 +66,7 @@ class FileManagerImpl(
}
fos.flush()
} catch (e: RuntimeException) {
return@withContext DownloadResult.Failure(error = e)
return@withContext DownloadResult.Failure
}
}
}
@@ -94,7 +94,7 @@ class FileManagerImpl(
}
}
true
} catch (_: RuntimeException) {
} catch (exception: RuntimeException) {
false
}
}
@@ -111,7 +111,7 @@ class FileManagerImpl(
}
}
true
} catch (_: RuntimeException) {
} catch (exception: RuntimeException) {
false
}
}

View File

@@ -16,8 +16,6 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
import com.x8bit.bitwarden.data.auth.repository.util.userAccountTokens
import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
@@ -169,7 +167,7 @@ class VaultLockManagerImpl(
.fold(
onFailure = {
incrementInvalidUnlockCount(userId = userId)
VaultUnlockResult.GenericError(error = it)
VaultUnlockResult.GenericError
},
onSuccess = { initializeCryptoResult ->
initializeCryptoResult
@@ -607,11 +605,9 @@ class VaultLockManagerImpl(
initUserCryptoMethod: InitUserCryptoMethod,
): VaultUnlockResult {
val account = authDiskSource.userState?.accounts?.get(userId)
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
?: return VaultUnlockResult.InvalidStateError
val privateKey = authDiskSource.getPrivateKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("Private key"),
)
?: return VaultUnlockResult.InvalidStateError
val organizationKeys = authDiskSource.getOrganizationKeys(userId = userId)
return unlockVault(
userId = userId,

View File

@@ -14,7 +14,5 @@ sealed class DownloadResult {
/**
* The download failed.
*/
data class Failure(
val error: Throwable,
) : DownloadResult()
data object Failure : DownloadResult()
}

View File

@@ -21,8 +21,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUpdatedUserStateJson
import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.datasource.network.util.isNoConnectionError
import com.x8bit.bitwarden.data.platform.error.MissingPropertyException
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
import com.x8bit.bitwarden.data.platform.manager.PushManager
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
@@ -511,13 +509,11 @@ class VaultRepositoryImpl(
): DecryptFido2CredentialAutofillViewResult {
return vaultSdkSource
.decryptFido2CredentialAutofillViews(
userId = activeUserId ?: return DecryptFido2CredentialAutofillViewResult.Error(
error = NoActiveUserException(),
),
userId = activeUserId ?: return DecryptFido2CredentialAutofillViewResult.Error,
cipherViews = cipherViewList.toTypedArray(),
)
.fold(
onFailure = { DecryptFido2CredentialAutofillViewResult.Error(error = it) },
onFailure = { DecryptFido2CredentialAutofillViewResult.Error },
onSuccess = { DecryptFido2CredentialAutofillViewResult.Success(it) },
)
}
@@ -549,13 +545,10 @@ class VaultRepositoryImpl(
)
override suspend fun unlockVaultWithBiometrics(cipher: Cipher): VaultUnlockResult {
val userId = activeUserId
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
val userId = activeUserId ?: return VaultUnlockResult.InvalidStateError
val biometricsKey = authDiskSource
.getUserBiometricUnlockKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("Biometric key"),
)
?: return VaultUnlockResult.InvalidStateError
val iv = authDiskSource.getUserBiometricInitVector(userId = userId)
return this
.unlockVaultForUser(
@@ -567,8 +560,8 @@ class VaultRepositoryImpl(
cipher
.doFinal(biometricsKey.toByteArray(Charsets.ISO_8859_1))
.decodeToString()
} catch (e: GeneralSecurityException) {
return VaultUnlockResult.BiometricDecodingError(error = e)
} catch (_: GeneralSecurityException) {
return VaultUnlockResult.BiometricDecodingError
}
}
?: biometricsKey,
@@ -593,12 +586,9 @@ class VaultRepositoryImpl(
override suspend fun unlockVaultWithMasterPassword(
masterPassword: String,
): VaultUnlockResult {
val userId = activeUserId
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
val userId = activeUserId ?: return VaultUnlockResult.InvalidStateError
val userKey = authDiskSource.getUserKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("User key"),
)
?: return VaultUnlockResult.InvalidStateError
return unlockVaultForUser(
userId = userId,
initUserCryptoMethod = InitUserCryptoMethod.Password(
@@ -616,12 +606,9 @@ class VaultRepositoryImpl(
override suspend fun unlockVaultWithPin(
pin: String,
): VaultUnlockResult {
val userId = activeUserId
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
val userId = activeUserId ?: return VaultUnlockResult.InvalidStateError
val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("Pin protected key"),
)
?: return VaultUnlockResult.InvalidStateError
return unlockVaultForUser(
userId = userId,
initUserCryptoMethod = InitUserCryptoMethod.Pin(
@@ -635,8 +622,7 @@ class VaultRepositoryImpl(
sendView: SendView,
fileUri: Uri?,
): CreateSendResult {
val userId = activeUserId
?: return CreateSendResult.Error(message = null, error = NoActiveUserException())
val userId = activeUserId ?: return CreateSendResult.Error(message = null)
return vaultSdkSource
.encryptSend(
userId = userId,
@@ -653,7 +639,6 @@ class VaultRepositoryImpl(
is CreateSendJsonResponse.Invalid -> {
return CreateSendResult.Error(
message = createSendResponse.firstValidationErrorMessage,
error = null,
)
}
@@ -671,7 +656,7 @@ class VaultRepositoryImpl(
)
}
.fold(
onFailure = { CreateSendResult.Error(message = null, error = it) },
onFailure = { CreateSendResult.Error(message = null) },
onSuccess = {
reviewPromptManager.registerCreateSendAction()
CreateSendResult.Success(it)
@@ -683,11 +668,7 @@ class VaultRepositoryImpl(
sendId: String,
sendView: SendView,
): UpdateSendResult {
val userId = activeUserId
?: return UpdateSendResult.Error(
errorMessage = null,
error = NoActiveUserException(),
)
val userId = activeUserId ?: return UpdateSendResult.Error(null)
return vaultSdkSource
.encryptSend(
userId = userId,
@@ -700,11 +681,11 @@ class VaultRepositoryImpl(
)
}
.fold(
onFailure = { UpdateSendResult.Error(errorMessage = null, error = it) },
onFailure = { UpdateSendResult.Error(errorMessage = null) },
onSuccess = { response ->
when (response) {
is UpdateSendResponseJson.Invalid -> {
UpdateSendResult.Error(errorMessage = response.message, error = null)
UpdateSendResult.Error(errorMessage = response.message)
}
is UpdateSendResponseJson.Success -> {
@@ -714,12 +695,9 @@ class VaultRepositoryImpl(
userId = userId,
send = response.send.toEncryptedSdkSend(),
)
.fold(
onSuccess = { UpdateSendResult.Success(sendView = it) },
onFailure = {
UpdateSendResult.Error(errorMessage = null, error = it)
},
)
.getOrNull()
?.let { UpdateSendResult.Success(sendView = it) }
?: UpdateSendResult.Error(errorMessage = null)
}
}
},
@@ -727,21 +705,14 @@ class VaultRepositoryImpl(
}
override suspend fun removePasswordSend(sendId: String): RemovePasswordSendResult {
val userId = activeUserId
?: return RemovePasswordSendResult.Error(
errorMessage = null,
error = NoActiveUserException(),
)
val userId = activeUserId ?: return RemovePasswordSendResult.Error(null)
return sendsService
.removeSendPassword(sendId = sendId)
.fold(
onSuccess = { response ->
when (response) {
is UpdateSendResponseJson.Invalid -> {
RemovePasswordSendResult.Error(
errorMessage = response.message,
error = null,
)
RemovePasswordSendResult.Error(errorMessage = response.message)
}
is UpdateSendResponseJson.Success -> {
@@ -751,30 +722,24 @@ class VaultRepositoryImpl(
userId = userId,
send = response.send.toEncryptedSdkSend(),
)
.fold(
onSuccess = { RemovePasswordSendResult.Success(sendView = it) },
onFailure = {
RemovePasswordSendResult.Error(
errorMessage = null,
error = it,
)
},
)
.getOrNull()
?.let { RemovePasswordSendResult.Success(sendView = it) }
?: RemovePasswordSendResult.Error(errorMessage = null)
}
}
},
onFailure = { RemovePasswordSendResult.Error(errorMessage = null, error = it) },
onFailure = { RemovePasswordSendResult.Error(errorMessage = null) },
)
}
override suspend fun deleteSend(sendId: String): DeleteSendResult {
val userId = activeUserId ?: return DeleteSendResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return DeleteSendResult.Error
return sendsService
.deleteSend(sendId)
.onSuccess { vaultDiskSource.deleteSend(userId, sendId) }
.fold(
onSuccess = { DeleteSendResult.Success },
onFailure = { DeleteSendResult.Error(error = it) },
onFailure = { DeleteSendResult.Error },
)
}
@@ -782,8 +747,7 @@ class VaultRepositoryImpl(
totpCode: String,
time: DateTime,
): GenerateTotpResult {
val userId = activeUserId
?: return GenerateTotpResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return GenerateTotpResult.Error
return vaultSdkSource.generateTotp(
time = time,
userId = userId,
@@ -796,13 +760,12 @@ class VaultRepositoryImpl(
periodSeconds = it.period.toInt(),
)
},
onFailure = { GenerateTotpResult.Error(error = it) },
onFailure = { GenerateTotpResult.Error },
)
}
override suspend fun createFolder(folderView: FolderView): CreateFolderResult {
val userId = activeUserId
?: return CreateFolderResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return CreateFolderResult.Error
return vaultSdkSource
.encryptFolder(
userId = userId,
@@ -818,7 +781,7 @@ class VaultRepositoryImpl(
.flatMap { vaultSdkSource.decryptFolder(userId, it.toEncryptedSdkFolder()) }
.fold(
onSuccess = { CreateFolderResult.Success(folderView = it) },
onFailure = { CreateFolderResult.Error(error = it) },
onFailure = { CreateFolderResult.Error },
)
}
@@ -826,10 +789,7 @@ class VaultRepositoryImpl(
folderId: String,
folderView: FolderView,
): UpdateFolderResult {
val userId = activeUserId ?: return UpdateFolderResult.Error(
errorMessage = null,
error = NoActiveUserException(),
)
val userId = activeUserId ?: return UpdateFolderResult.Error(null)
return vaultSdkSource
.encryptFolder(
userId = userId,
@@ -854,30 +814,21 @@ class VaultRepositoryImpl(
)
.fold(
onSuccess = { UpdateFolderResult.Success(it) },
onFailure = {
UpdateFolderResult.Error(
errorMessage = null,
error = it,
)
},
onFailure = { UpdateFolderResult.Error(errorMessage = null) },
)
}
is UpdateFolderResponseJson.Invalid -> {
UpdateFolderResult.Error(
errorMessage = response.message,
error = null,
)
UpdateFolderResult.Error(response.message)
}
}
},
onFailure = { UpdateFolderResult.Error(it.message, error = it) },
onFailure = { UpdateFolderResult.Error(it.message) },
)
}
override suspend fun deleteFolder(folderId: String): DeleteFolderResult {
val userId = activeUserId
?: return DeleteFolderResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return DeleteFolderResult.Error
return folderService
.deleteFolder(
folderId = folderId,
@@ -888,7 +839,7 @@ class VaultRepositoryImpl(
}
.fold(
onSuccess = { DeleteFolderResult.Success },
onFailure = { DeleteFolderResult.Error(error = it) },
onFailure = { DeleteFolderResult.Error },
)
}
@@ -903,8 +854,7 @@ class VaultRepositoryImpl(
}
override suspend fun exportVaultDataToString(format: ExportFormat): ExportVaultDataResult {
val userId = activeUserId
?: return ExportVaultDataResult.Error(error = NoActiveUserException())
val userId = activeUserId ?: return ExportVaultDataResult.Error
val folders = vaultDiskSource
.getFolders(userId)
.firstOrNull()
@@ -927,7 +877,7 @@ class VaultRepositoryImpl(
)
.fold(
onSuccess = { ExportVaultDataResult.Success(it) },
onFailure = { ExportVaultDataResult.Error(error = it) },
onFailure = { ExportVaultDataResult.Error },
)
}
@@ -995,11 +945,9 @@ class VaultRepositoryImpl(
initUserCryptoMethod: InitUserCryptoMethod,
): VaultUnlockResult {
val account = authDiskSource.userState?.accounts?.get(userId)
?: return VaultUnlockResult.InvalidStateError(error = NoActiveUserException())
?: return VaultUnlockResult.InvalidStateError
val privateKey = authDiskSource.getPrivateKey(userId = userId)
?: return VaultUnlockResult.InvalidStateError(
error = MissingPropertyException("Private key"),
)
?: return VaultUnlockResult.InvalidStateError
val organizationKeys = authDiskSource
.getOrganizationKeys(userId = userId)
return unlockVault(

View File

@@ -17,5 +17,5 @@ sealed class CreateAttachmentResult {
/**
* Generic error while creating an attachment.
*/
data class Error(val error: Throwable) : CreateAttachmentResult()
data object Error : CreateAttachmentResult()
}

View File

@@ -13,5 +13,5 @@ sealed class CreateCipherResult {
/**
* Generic error while creating cipher.
*/
data class Error(val error: Throwable) : CreateCipherResult()
data object Error : CreateCipherResult()
}

View File

@@ -15,5 +15,5 @@ sealed class CreateFolderResult {
/**
* Generic error while creating a folder.
*/
data class Error(val error: Throwable) : CreateFolderResult()
data object Error : CreateFolderResult()
}

View File

@@ -15,5 +15,5 @@ sealed class CreateSendResult {
/**
* Generic error while creating a send.
*/
data class Error(val message: String?, val error: Throwable?) : CreateSendResult()
data class Error(val message: String?) : CreateSendResult()
}

View File

@@ -16,5 +16,5 @@ sealed class DecryptFido2CredentialAutofillViewResult {
/**
* Generic error while decrypting credentials.
*/
data class Error(val error: Throwable) : DecryptFido2CredentialAutofillViewResult()
data object Error : DecryptFido2CredentialAutofillViewResult()
}

View File

@@ -13,5 +13,5 @@ sealed class DeleteAttachmentResult {
/**
* Generic error while deleting an attachment.
*/
data class Error(val error: Throwable) : DeleteAttachmentResult()
data object Error : DeleteAttachmentResult()
}

View File

@@ -13,5 +13,5 @@ sealed class DeleteCipherResult {
/**
* Generic error while deleting a cipher.
*/
data class Error(val error: Throwable) : DeleteCipherResult()
data object Error : DeleteCipherResult()
}

View File

@@ -13,5 +13,5 @@ sealed class DeleteFolderResult {
/**
* Generic error while deleting a folder.
*/
data class Error(val error: Throwable) : DeleteFolderResult()
data object Error : DeleteFolderResult()
}

View File

@@ -13,5 +13,5 @@ sealed class DeleteSendResult {
/**
* Generic error while deleting a send.
*/
data class Error(val error: Throwable) : DeleteSendResult()
data object Error : DeleteSendResult()
}

View File

@@ -14,5 +14,5 @@ sealed class DownloadAttachmentResult {
/**
* The attachment could not be downloaded.
*/
data class Failure(val error: Throwable) : DownloadAttachmentResult()
data object Failure : DownloadAttachmentResult()
}

View File

@@ -14,5 +14,5 @@ sealed class ExportVaultDataResult {
/**
* There was an error converting the vault data.
*/
data class Error(val error: Throwable) : ExportVaultDataResult()
data object Error : ExportVaultDataResult()
}

View File

@@ -16,5 +16,5 @@ sealed class GenerateTotpResult {
/**
* An error occurred while generating the code.
*/
data class Error(val error: Throwable) : GenerateTotpResult()
data object Error : GenerateTotpResult()
}

View File

@@ -17,5 +17,5 @@ sealed class RemovePasswordSendResult {
* Generic error while removing the password protection from a send. The optional
* [errorMessage] may be displayed directly in the UI when present.
*/
data class Error(val errorMessage: String?, val error: Throwable?) : RemovePasswordSendResult()
data class Error(val errorMessage: String?) : RemovePasswordSendResult()
}

View File

@@ -13,5 +13,5 @@ sealed class RestoreCipherResult {
/**
* Generic error while restoring a cipher.
*/
data class Error(val error: Throwable) : RestoreCipherResult()
data object Error : RestoreCipherResult()
}

View File

@@ -12,5 +12,5 @@ sealed class ShareCipherResult {
/**
* Generic error while sharing cipher.
*/
data class Error(val error: Throwable) : ShareCipherResult()
data object Error : ShareCipherResult()
}

View File

@@ -13,5 +13,5 @@ sealed class TotpCodeResult {
/**
* There was an error scanning the code.
*/
data class CodeScanningError(val error: Throwable? = null) : TotpCodeResult()
data object CodeScanningError : TotpCodeResult()
}

View File

@@ -14,5 +14,5 @@ sealed class UpdateCipherResult {
* Generic error while updating cipher. The optional [errorMessage] may be displayed directly in
* the UI when present.
*/
data class Error(val errorMessage: String?, val error: Throwable?) : UpdateCipherResult()
data class Error(val errorMessage: String?) : UpdateCipherResult()
}

View File

@@ -16,5 +16,5 @@ sealed class UpdateFolderResult {
* Generic error while updating a folder. The optional [errorMessage]
* may be displayed directly in the UI when present.
*/
data class Error(val errorMessage: String?, val error: Throwable?) : UpdateFolderResult()
data class Error(val errorMessage: String?) : UpdateFolderResult()
}

View File

@@ -16,5 +16,5 @@ sealed class UpdateSendResult {
* Generic error while updating a send. The optional [errorMessage] may be displayed directly
* in the UI when present.
*/
data class Error(val errorMessage: String?, val error: Throwable?) : UpdateSendResult()
data class Error(val errorMessage: String?) : UpdateSendResult()
}

View File

@@ -15,34 +15,25 @@ sealed class VaultUnlockResult {
*/
data class AuthenticationError(
val message: String? = null,
override val error: Throwable?,
) : VaultUnlockResult(), VaultUnlockError
/**
* Unable to decode biometrics key.
*/
data class BiometricDecodingError(
override val error: Throwable?,
) : VaultUnlockResult(), VaultUnlockError
data object BiometricDecodingError : VaultUnlockResult(), VaultUnlockError
/**
* Unable to access user state information.
*/
data class InvalidStateError(
override val error: Throwable?,
) : VaultUnlockResult(), VaultUnlockError
data object InvalidStateError : VaultUnlockResult(), VaultUnlockError
/**
* Generic error thrown by Bitwarden SDK.
*/
data class GenericError(
override val error: Throwable?,
) : VaultUnlockResult(), VaultUnlockError
data object GenericError : VaultUnlockResult(), VaultUnlockError
}
/**
* Sealed interface to denote that a [VaultUnlockResult] is an error result.
*/
sealed interface VaultUnlockError {
val error: Throwable?
}
sealed interface VaultUnlockError

View File

@@ -11,7 +11,6 @@ fun InitializeCryptoResult.toVaultUnlockResult(): VaultUnlockResult =
is InitializeCryptoResult.AuthenticationError -> {
VaultUnlockResult.AuthenticationError(
message = this.message,
error = error,
)
}

View File

@@ -164,7 +164,7 @@ class SetupUnlockViewModel @Inject constructor(
settingsRepository.storeUnlockPin(
pin = state.pin,
shouldRequireMasterPasswordOnRestart =
state.shouldRequireMasterPasswordOnRestart,
state.shouldRequireMasterPasswordOnRestart,
)
}
}
@@ -182,7 +182,7 @@ class SetupUnlockViewModel @Inject constructor(
action: SetupUnlockAction.Internal.BiometricsKeyResultReceive,
) {
when (action.result) {
is BiometricsKeyResult.Error -> {
BiometricsKeyResult.Error -> {
mutableStateFlow.update {
it.copy(
dialogState = null,

View File

@@ -108,7 +108,6 @@ fun CompleteRegistrationScreen(
BitwardenBasicDialog(
title = dialog.title?.invoke(),
message = dialog.message(),
throwable = dialog.error,
onDismissRequest = handler.onDismissErrorDialog,
)
}
@@ -142,8 +141,7 @@ fun CompleteRegistrationScreen(
title = stringResource(
id = R.string.create_account
.takeIf { state.onboardingEnabled }
?: R.string.set_password,
),
?: R.string.set_password),
scrollBehavior = scrollBehavior,
navigationIcon = rememberVectorPainter(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),

View File

@@ -187,7 +187,7 @@ class CompleteRegistrationViewModel @Inject constructor(
}
}
is PasswordStrengthResult.Error -> Unit
PasswordStrengthResult.Error -> Unit
}
}
@@ -210,7 +210,6 @@ class CompleteRegistrationViewModel @Inject constructor(
title = R.string.an_error_has_occurred.asText(),
message = registerAccountResult.errorMessage?.asText()
?: R.string.generic_error_message.asText(),
error = registerAccountResult.error,
),
)
}
@@ -526,7 +525,6 @@ sealed class CompleteRegistrationDialog : Parcelable {
data class Error(
val title: Text?,
val message: Text,
val error: Throwable? = null,
) : CompleteRegistrationDialog()
}

View File

@@ -116,7 +116,6 @@ fun CreateAccountScreen(
BitwardenBasicDialog(
title = dialog.title?.invoke(),
message = dialog.message(),
throwable = dialog.error,
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(ErrorDialogDismiss) }
},

View File

@@ -128,7 +128,7 @@ class CreateAccountViewModel @Inject constructor(
}
}
is PasswordStrengthResult.Error -> {
PasswordStrengthResult.Error -> {
// Leave UI the same
}
}
@@ -180,7 +180,6 @@ class CreateAccountViewModel @Inject constructor(
title = R.string.an_error_has_occurred.asText(),
message = registerAccountResult.errorMessage?.asText()
?: R.string.generic_error_message.asText(),
error = registerAccountResult.error,
),
)
}
@@ -465,7 +464,6 @@ sealed class CreateAccountDialog : Parcelable {
data class Error(
val title: Text?,
val message: Text,
val error: Throwable? = null,
) : CreateAccountDialog()
}

Some files were not shown because too many files have changed in this diff Show More