mirror of
https://github.com/bitwarden/android.git
synced 2026-06-19 20:25:08 -05:00
Compare commits
4 Commits
qrcode/fea
...
release-no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2ac7cac9 | ||
|
|
5bbbf7c5e4 | ||
|
|
39d10fa77d | ||
|
|
f0f240f8e5 |
64
.github/ISSUE_TEMPLATE/bug-passkey.yml
vendored
64
.github/ISSUE_TEMPLATE/bug-passkey.yml
vendored
@@ -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
51
.github/renovate.json
vendored
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
64
.github/scripts/release-notes/linked_issues.py
vendored
Normal file
64
.github/scripts/release-notes/linked_issues.py
vendored
Normal 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 []
|
||||
112
.github/scripts/release-notes/process_release_notes.py
vendored
Normal file
112
.github/scripts/release-notes/process_release_notes.py
vendored
Normal 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)
|
||||
4
.github/scripts/release-notes/pyproject.toml
vendored
Normal file
4
.github/scripts/release-notes/pyproject.toml
vendored
Normal 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"
|
||||
30
.github/scripts/release-notes/test_linked_issues.py
vendored
Normal file
30
.github/scripts/release-notes/test_linked_issues.py
vendored
Normal 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()
|
||||
95
.github/scripts/release-notes/test_process_release_notes.py
vendored
Normal file
95
.github/scripts/release-notes/test_process_release_notes.py
vendored
Normal 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
6
.gitignore
vendored
@@ -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
|
||||
|
||||
27
Gemfile.lock
27
Gemfile.lock
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -22,7 +22,5 @@ sealed class OrganizationDomainSsoDetailsResult {
|
||||
/**
|
||||
* The request failed.
|
||||
*/
|
||||
data class Failure(
|
||||
val error: Throwable,
|
||||
) : OrganizationDomainSsoDetailsResult()
|
||||
data object Failure : OrganizationDomainSsoDetailsResult()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -16,6 +16,5 @@ sealed class PrevalidateSsoResult {
|
||||
*/
|
||||
data class Failure(
|
||||
val message: String? = null,
|
||||
val error: Throwable?,
|
||||
) : PrevalidateSsoResult()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,5 @@ sealed class VerifiedOrganizationDomainSsoDetailsResult {
|
||||
/**
|
||||
* The request failed.
|
||||
*/
|
||||
data class Failure(
|
||||
val error: Throwable,
|
||||
) : VerifiedOrganizationDomainSsoDetailsResult()
|
||||
data object Failure : VerifiedOrganizationDomainSsoDetailsResult()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ class Fido2ProviderProcessorImpl(
|
||||
val result = vaultRepository
|
||||
.getDecryptedFido2CredentialAutofillViews(cipherViews)
|
||||
return when (result) {
|
||||
is DecryptFido2CredentialAutofillViewResult.Error -> {
|
||||
DecryptFido2CredentialAutofillViewResult.Error -> {
|
||||
throw GetCredentialUnknownException("Error decrypting credentials.")
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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!")
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -15,6 +15,5 @@ sealed class InitializeCryptoResult {
|
||||
*/
|
||||
data class AuthenticationError(
|
||||
val message: String? = null,
|
||||
val error: Throwable,
|
||||
) : InitializeCryptoResult()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,7 +14,5 @@ sealed class DownloadResult {
|
||||
/**
|
||||
* The download failed.
|
||||
*/
|
||||
data class Failure(
|
||||
val error: Throwable,
|
||||
) : DownloadResult()
|
||||
data object Failure : DownloadResult()
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -17,5 +17,5 @@ sealed class CreateAttachmentResult {
|
||||
/**
|
||||
* Generic error while creating an attachment.
|
||||
*/
|
||||
data class Error(val error: Throwable) : CreateAttachmentResult()
|
||||
data object Error : CreateAttachmentResult()
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ sealed class CreateCipherResult {
|
||||
/**
|
||||
* Generic error while creating cipher.
|
||||
*/
|
||||
data class Error(val error: Throwable) : CreateCipherResult()
|
||||
data object Error : CreateCipherResult()
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ sealed class CreateFolderResult {
|
||||
/**
|
||||
* Generic error while creating a folder.
|
||||
*/
|
||||
data class Error(val error: Throwable) : CreateFolderResult()
|
||||
data object Error : CreateFolderResult()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -16,5 +16,5 @@ sealed class DecryptFido2CredentialAutofillViewResult {
|
||||
/**
|
||||
* Generic error while decrypting credentials.
|
||||
*/
|
||||
data class Error(val error: Throwable) : DecryptFido2CredentialAutofillViewResult()
|
||||
data object Error : DecryptFido2CredentialAutofillViewResult()
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ sealed class DeleteAttachmentResult {
|
||||
/**
|
||||
* Generic error while deleting an attachment.
|
||||
*/
|
||||
data class Error(val error: Throwable) : DeleteAttachmentResult()
|
||||
data object Error : DeleteAttachmentResult()
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ sealed class DeleteCipherResult {
|
||||
/**
|
||||
* Generic error while deleting a cipher.
|
||||
*/
|
||||
data class Error(val error: Throwable) : DeleteCipherResult()
|
||||
data object Error : DeleteCipherResult()
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ sealed class DeleteFolderResult {
|
||||
/**
|
||||
* Generic error while deleting a folder.
|
||||
*/
|
||||
data class Error(val error: Throwable) : DeleteFolderResult()
|
||||
data object Error : DeleteFolderResult()
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ sealed class DeleteSendResult {
|
||||
/**
|
||||
* Generic error while deleting a send.
|
||||
*/
|
||||
data class Error(val error: Throwable) : DeleteSendResult()
|
||||
data object Error : DeleteSendResult()
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@ sealed class DownloadAttachmentResult {
|
||||
/**
|
||||
* The attachment could not be downloaded.
|
||||
*/
|
||||
data class Failure(val error: Throwable) : DownloadAttachmentResult()
|
||||
data object Failure : DownloadAttachmentResult()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ sealed class RestoreCipherResult {
|
||||
/**
|
||||
* Generic error while restoring a cipher.
|
||||
*/
|
||||
data class Error(val error: Throwable) : RestoreCipherResult()
|
||||
data object Error : RestoreCipherResult()
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@ sealed class ShareCipherResult {
|
||||
/**
|
||||
* Generic error while sharing cipher.
|
||||
*/
|
||||
data class Error(val error: Throwable) : ShareCipherResult()
|
||||
data object Error : ShareCipherResult()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,6 @@ fun InitializeCryptoResult.toVaultUnlockResult(): VaultUnlockResult =
|
||||
is InitializeCryptoResult.AuthenticationError -> {
|
||||
VaultUnlockResult.AuthenticationError(
|
||||
message = this.message,
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,6 @@ fun CreateAccountScreen(
|
||||
BitwardenBasicDialog(
|
||||
title = dialog.title?.invoke(),
|
||||
message = dialog.message(),
|
||||
throwable = dialog.error,
|
||||
onDismissRequest = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ErrorDialogDismiss) }
|
||||
},
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user