Compare commits

..

8 Commits

1785 changed files with 25615 additions and 102087 deletions

View File

@@ -8,4 +8,4 @@ checkmarx:
configs:
sast:
# Exclude test directories
filter: "**/test/**,!**/androidTest/**,!**/commonTest/**,!**/jvmTest/**,!**/jsTest/**,!**/iosTest/**"
filter: "!app/src/test/**"

View File

@@ -12,20 +12,127 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
guidelines = 120
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# Kotlin files
# noinspection EditorConfigKeyCorrectness
[*.{kt,kts}]
# https://pinterest.github.io/ktlint/1.0.1/rules/configuration-ktlint/#trailing-comma-on-declaration-site
ij_kotlin_allow_trailing_comma = true
# https://pinterest.github.io/ktlint/1.0.1/rules/configuration-ktlint/#trailing-comma-on-declaration-site
trailing-comma-on-declaration-site = true
# https://pinterest.github.io/ktlint/1.0.1/rules/configuration-ktlint/#trailing-comma-on-call-site
ij_kotlin_allow_trailing_comma_on_call_site = true
[*.{scss,yml}]
# JS files
[*.{js,ts,scss,html}]
indent_size = 2
[*.{ts}]
quote_type = single
[*.{scss,yml,csproj}]
indent_size = 2
[*.sln]
indent_style = tab
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Prefix private members with underscore
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.capitalization = camel_case
dotnet_naming_style.prefix_underscore.required_prefix = _
# Async methods should have "Async" suffix
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
dotnet_naming_rule.async_methods_end_in_async.severity = suggestion
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
dotnet_naming_symbols.any_async_methods.required_modifiers = async
dotnet_naming_style.end_in_async.required_prefix =
dotnet_naming_style.end_in_async.required_suffix = Async
dotnet_naming_style.end_in_async.capitalization = pascal_case
dotnet_naming_style.end_in_async.word_separator =
# Obsolete warnings, this should be removed or changed to warning once we address some of the obsolete items.
dotnet_diagnostic.CS0618.severity = suggestion
# Obsolete warnings, this should be removed or changed to warning once we address some of the obsolete items.
dotnet_diagnostic.CS0612.severity = suggestion
# Remove unnecessary using directives https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0005
dotnet_diagnostic.IDE0005.severity = warning
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a expression-body
csharp_style_expression_bodied_methods = true:none
csharp_style_expression_bodied_constructors = true:none
csharp_style_expression_bodied_operators = true:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
# Namespace settings
csharp_style_namespace_declarations = file_scoped:warning
# Switch expression
dotnet_diagnostic.CS8509.severity = error # missing switch case for named enum value
dotnet_diagnostic.CS8524.severity = none # missing switch case for unnamed enum value

4
.github/CODEOWNERS vendored
View File

@@ -5,10 +5,10 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Default file owners.
* @bitwarden/team-android @brian-livefront @david-livefront
* @bitwarden/team-android @brian-livefront @david-livefront @dseverns-livefront @ahaisting-livefront
# Actions and workflow changes.
.github/ @bitwarden/dept-development-mobile
.github/workflows @bitwarden/dept-development-mobile
# Auth
# app/src/main/java/com/x8bit/bitwarden/data/auth @bitwarden/team-auth-dev

View File

@@ -1,84 +0,0 @@
name: Authenticator Android App Bug Report
description: File a bug report
labels: [ "app:authenticator", "bug" ]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: textarea
id: reproduce
attributes:
label: Steps To Reproduce
description: How can we reproduce the behavior.
value: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. Click on '...'
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Result
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Result
description: A clear and concise description of what is happening.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots or Videos
description: If applicable, add screenshots and/or a short video to help explain your problem.
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context about the problem here.
- type: input
id: version
attributes:
label: Build Version
description: What version of our software are you running?
validations:
required: true
- type: dropdown
id: server-region
attributes:
label: What server are you connecting to?
options:
- US
- EU
- Self-host
- N/A
validations:
required: true
- type: input
id: server-version
attributes:
label: Self-host Server Version
description: If self-hosting, what version of Bitwarden Server are you running?
- type: textarea
id: environment-details
attributes:
label: Environment Details
placeholder: |
- Device: [e.g. Pixel Tablet, Samsung Galaxy S24 ]
- OS Version: [e.g. API 32, Tiramisu ]
- 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.

View File

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

View File

@@ -1,6 +1,6 @@
name: Password Manager Android App Bug Report
name: Android Bug Report
description: File a bug report
labels: [ "app:password-manager", "bug" ]
labels: [ bug ]
body:
- type: markdown
attributes:

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Legacy Android Bug Reports
url: https://github.com/bitwarden/mobile/issues
about: Bugs found in the publicly available .NET MAUI app should be reported in [bitwarden/mobile](https://github.com/bitwarden/mobile)
- name: Feature Requests
url: https://community.bitwarden.com/c/feature-requests/
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
@@ -12,5 +15,3 @@ contact_links:
- name: Security Issues
url: https://hackerone.com/bitwarden
about: We use HackerOne to manage security disclosures.
- name: Report mobile autofill failure
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform

View File

@@ -15,11 +15,10 @@
- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team
- Updated any necessary documentation or informed the documentation team
## 🦮 Reviewer guidelines
@@ -28,7 +27,8 @@
- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed
issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt

2
.github/codecov.yml vendored
View File

@@ -1,2 +0,0 @@
ignore:
- "src/test/**" # Tests

50
.github/renovate.json vendored
View File

@@ -1,56 +1,32 @@
{
"$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",
"org.jetbrains.kotlin.*",
"com.google.devtools.ksp"
],
"matchPackageNames": [
"/androidx.compose:compose-bom/",
"/androidx.lifecycle:*/",
"/org.jetbrains.kotlin.*/",
"/com.google.devtools.ksp/"
]
"matchManagers": ["gradle"]
},
{
"groupName": "bundler minor",
"matchUpdateTypes": [
"minor",
"patch"
],
"matchManagers": [
"bundler"
]
"matchUpdateTypes": ["minor", "patch"],
"matchManagers": ["bundler"]
}
]
}

View File

@@ -1 +0,0 @@
3.13

View File

@@ -1,39 +0,0 @@
# JSON Validation Scripts
Utility scripts for validating JSON files and checking for duplicate package names between Google and Community privileged browser lists.
## Usage
### Validate a JSON file
```bash
python validate_json.py validate <json_file>
```
### Check for duplicates between two JSON files
```bash
python validate_json.py duplicates <json_file1> <json_file2> [output_file]
```
If `output_file` is not specified, duplicates will be saved to `duplicates.txt`.
## Running Tests
```bash
# Run all tests
python -m unittest test_validate_json.py
# Run the invalid JSON test individually
python -m unittest test_validate_json.TestValidateJson.test_validate_json_invalid
```
## Examples
```bash
# Validate Google privileged browsers list
python validate_json.py validate ../../app/src/main/assets/fido2_privileged_google.json
# Check for duplicates between Google and Community lists
python validate_json.py duplicates ../../app/src/main/assets/fido2_privileged_google.json ../../app/src/main/assets/fido2_privileged_community.json duplicates.txt
```

View File

@@ -1,20 +0,0 @@
{
"apps": [
"type": "android",
"info": {
"package_name": "com.android.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
}
]
}

View File

@@ -1,48 +0,0 @@
{
"apps": [
{
"type": "android",
"info": {
"package_name": "com.android.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.dev",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "90:44:EE:5F:EE:4B:BC:5E:21:DD:44:66:54:31:C4:EB:1F:1F:71:A3:27:16:A0:BC:92:7B:CB:B3:92:33:CA:BF"
},
{
"build": "release",
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "DF:A1:FB:23:EF:BF:70:C5:BC:D1:44:3C:5B:EA:B0:4F:3F:2F:F4:36:6E:9A:C1:E3:45:76:39:A2:4C:FC"
}
]
}
}
]
}

View File

@@ -1,20 +0,0 @@
{
"apps": [
{
"type": "android",
"info": {
"package_name": "org.chromium.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
}
]
}

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env python3
import unittest
import os
import json
from validate_json import validate_json, find_duplicates, get_package_names
from unittest.mock import patch
import io
class TestValidateJson(unittest.TestCase):
def setUp(self):
self.valid_file = os.path.join(os.path.dirname(__file__), "fixtures/sample-valid1.json")
self.valid_file2 = os.path.join(os.path.dirname(__file__), "fixtures/sample-valid2.json")
self.invalid_file = os.path.join(os.path.dirname(__file__), "fixtures/sample-invalid.json")
# Suppress stdout
self.stdout_patcher = patch('sys.stdout', new=io.StringIO())
self.stdout_patcher.start()
def tearDown(self):
self.stdout_patcher.stop()
def test_validate_json_valid(self):
"""Test validation of valid JSON file"""
self.assertTrue(validate_json(self.valid_file))
def test_validate_json_invalid(self):
"""Test validation of invalid JSON file"""
self.assertFalse(validate_json(self.invalid_file))
def test_find_duplicates(self):
"""Test when using the same file (should find duplicates)"""
expected_package_names = get_package_names(self.valid_file)
duplicates = find_duplicates(self.valid_file, self.valid_file)
self.assertEqual(len(duplicates), len(expected_package_names))
for package_name in expected_package_names:
self.assertIn(package_name, duplicates)
def test_find_duplicates_returns_empty_list_when_no_duplicates(self):
"""Test when using different files (should not find duplicates)"""
duplicates = find_duplicates(self.valid_file, self.valid_file2)
self.assertEqual(len(duplicates), 0)
if __name__ == "__main__":
unittest.main()

View File

@@ -1,145 +0,0 @@
#!/usr/bin/env python3
import json
import sys
import os
from typing import List, Dict, Any, Set
def get_package_names(file_path: str) -> Set[str]:
"""
Extracts package names from a JSON file.
Args:
file_path: Path to the JSON file
Returns:
Set of package names
"""
with open(file_path, 'r') as f:
data = json.load(f)
package_names = set()
for app in data["apps"]:
package_names.add(app["info"]["package_name"])
return package_names
def validate_json(file_path: str) -> bool:
"""
Validates if a JSON file is correctly formatted by attempting to deserialize it.
Args:
file_path: Path to the JSON file to validate
Returns:
True if valid, False otherwise
"""
try:
if not os.path.exists(file_path):
print(f"Error: File {file_path} does not exist")
return False
with open(file_path, 'r') as f:
json.load(f)
print(f"✅ JSON file {file_path} is valid")
return True
except json.JSONDecodeError as e:
print(f"❌ Invalid JSON in {file_path}: {str(e)}")
return False
except Exception as e:
print(f"❌ Error validating {file_path}: {str(e)}")
return False
def find_duplicates(file1_path: str, file2_path: str) -> List[str]:
"""
Checks for duplicate package_name entries between two JSON files.
Args:
file1_path: Path to the first JSON file
file2_path: Path to the second JSON file
Returns:
List of duplicate package names, empty list if none found
"""
try:
# Get package names from both files
packages1 = get_package_names(file1_path)
packages2 = get_package_names(file2_path)
# Find duplicates
duplicates = list(packages1.intersection(packages2))
if duplicates:
print(f"❌ Found {len(duplicates)} duplicate package names between {file1_path} and {file2_path}:")
for dup in duplicates:
print(f" - {dup}")
return duplicates
else:
print(f"✅ No duplicate package names found between {file1_path} and {file2_path}")
return []
except Exception as e:
print(f"❌ Error checking duplicates: {str(e)}")
return []
def save_duplicates_to_file(duplicates: List[str], output_file: str) -> None:
"""
Saves the list of duplicates to a file.
Args:
duplicates: List of duplicate package names
output_file: Path to save the list of duplicates
"""
try:
with open(output_file, 'w') as f:
for dup in duplicates:
f.write(f"{dup}\n")
print(f"Duplicates saved to {output_file}")
except Exception as e:
print(f"❌ Error saving duplicates to file: {str(e)}")
def main():
if len(sys.argv) < 2:
print("Usage:")
print(" Validate JSON: python validate_json.py validate <json_file>")
print(" Check duplicates: python validate_json.py duplicates <json_file1> <json_file2> [output_file]")
sys.exit(1)
command = sys.argv[1]
match command:
case "validate":
if len(sys.argv) < 3:
print("Error: Missing JSON file path")
sys.exit(1)
file_path = sys.argv[2]
success = validate_json(file_path)
sys.exit(0 if success else 1)
case "duplicates":
if len(sys.argv) < 4:
print("Error: Missing JSON file paths")
sys.exit(1)
file1_path = sys.argv[2]
file2_path = sys.argv[3]
output_file = sys.argv[4] if len(sys.argv) > 4 else "duplicates.txt"
duplicates = find_duplicates(file1_path, file2_path)
if duplicates:
save_duplicates_to_file(duplicates, output_file)
sys.exit(0)
case _:
print(f"Unknown command: {command}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,289 +0,0 @@
name: Build Authenticator
on:
push:
branches:
- main
workflow_dispatch:
inputs:
version-name:
description: "Optional. Version string to use, in X.Y.Z format. Overrides default in the project."
required: false
type: string
version-code:
description: "Optional. Build number to use. Overrides default of GitHub run number."
required: false
type: number
distribute-to-firebase:
description: "Optional. Distribute artifacts to Firebase."
required: false
default: false
type: boolean
publish-to-play-store:
description: "Optional. Deploy bundle artifact to Google Play Store"
required: false
default: false
type: boolean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_VERSION: 17
jobs:
build:
name: Build Authenticator
runs-on: ubuntu-24.04
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
restore-keys: |
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
${{ github.workspace }}/build-cache
key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
- name: Install Fastlane
run: |
gem install bundler:2.2.27
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Check Authenticator
run: bundle exec fastlane check
- name: Build Authenticator
run: bundle exec fastlane buildAuthenticatorDebug
publish_playstore:
name: Publish Authenticator Play Store artifacts
needs:
- build
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
variant: ["aab", "apk"]
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
- name: Install Fastlane
run: |
gem install bundler:2.2.27
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Log in to Azure
uses: Azure/login@cb79c773a3cfa27f31f25eb3f677781210c9ce3d # v1.6.1
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
run: |
mkdir -p ${{ github.workspace }}/secrets
mkdir -p ${{ github.workspace }}/keystores
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name authenticator_apk-keystore.jks --file ${{ github.workspace }}/keystores/authenticator_apk-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name authenticator_aab-keystore.jks --file ${{ github.workspace }}/keystores/authenticator_aab-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name com.bitwarden.authenticator-google-services.json --file ${{ github.workspace }}/authenticator/src/google-services.json --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name com.bitwarden.authenticator.dev-google-services.json --file ${{ github.workspace }}/authenticator/src/debug/google-services.json --output none
- name: Download Firebase credentials
if : ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
run: |
mkdir -p ${{ github.workspace }}/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name authenticator_play_firebase-creds.json --file ${{ github.workspace }}/secrets/authenticator_play_firebase-creds.json --output none
- name: Download Play Store credentials
if: ${{ inputs.publish-to-play-store }}
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
run: |
mkdir -p ${{ github.workspace }}/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name authenticator_play_store-creds.json --file ${{ github.workspace }}/secrets/authenticator_play_store-creds.json --output none
- name: Verify Play Store credentials
if: ${{ inputs.publish-to-play-store }}
run: |
bundle exec fastlane run validate_play_store_json_key \
json_key:${{ github.workspace }}/secrets/authenticator_play_store-creds.json }}
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
restore-keys: |
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
${{ github.workspace }}/build-cache
key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Increment version
run: |
DEFAULT_VERSION_CODE=$GITHUB_RUN_NUMBER
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
bundle exec fastlane setAuthenticatorBuildVersionInfo \
versionCode:$VERSION_CODE \
versionName:${{ inputs.version-name || '' }}
regex='versionName = "([^"]+)"'
if [[ "$(cat authenticator/build.gradle.kts)" =~ $regex ]]; then
VERSION_NAME="${BASH_REMATCH[1]}"
fi
echo "Version Name: ${VERSION_NAME}" >> $GITHUB_STEP_SUMMARY
echo "Version Number: $VERSION_CODE" >> $GITHUB_STEP_SUMMARY
- name: Generate release Play Store bundle
if: ${{ matrix.variant == 'aab' }}
run: |
bundle exec fastlane bundleAuthenticatorRelease \
storeFile:${{ github.workspace }}/keystores/authenticator_aab-keystore.jks \
storePassword:'${{ secrets.BWA_AAB_KEYSTORE_STORE_PASSWORD }}' \
keyAlias:authenticatorupload \
keyPassword:'${{ secrets.BWA_AAB_KEYSTORE_KEY_PASSWORD }}'
- name: Generate release Play Store APK
if: ${{ matrix.variant == 'apk' }}
run: |
bundle exec fastlane buildAuthenticatorRelease \
storeFile:${{ github.workspace }}/keystores/authenticator_apk-keystore.jks \
storePassword:'${{ secrets.BWA_APK_KEYSTORE_STORE_PASSWORD }}' \
keyAlias:bitwardenauthenticator \
keyPassword:'${{ secrets.BWA_APK_KEYSTORE_KEY_PASSWORD }}'
- name: Upload release Play Store .aab artifact
if: ${{ matrix.variant == 'aab' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.bitwarden.authenticator.aab
path: authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab
if-no-files-found: error
- name: Upload release .apk artifact
if: ${{ matrix.variant == 'apk' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.bitwarden.authenticator.apk
path: authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk
if-no-files-found: error
- name: Create checksum file for Release AAB
if: ${{ matrix.variant == 'aab' }}
run: |
sha256sum "authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab" \
> ./authenticator-android-aab-sha256.txt
- name: Create checksum for release .apk artifact
if: ${{ matrix.variant == 'apk' }}
run: |
sha256sum "authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk" \
> ./authenticator-android-apk-sha256.txt
- name: Upload .apk SHA file for release
if: ${{ matrix.variant == 'apk' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: authenticator-android-apk-sha256.txt
path: ./authenticator-android-apk-sha256.txt
if-no-files-found: error
- name: Upload .aab SHA file for release
if: ${{ matrix.variant == 'aab' }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: authenticator-android-aab-sha256.txt
path: ./authenticator-android-aab-sha256.txt
if-no-files-found: error
- name: Install Firebase app distribution plugin
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
run: bundle exec fastlane add_plugin firebase_app_distribution
- name: Publish release bundle to Firebase
if: ${{ matrix.variant == 'aab' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
env:
FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/authenticator_play_firebase-creds.json
run: |
bundle exec fastlane distributeAuthenticatorReleaseBundleToFirebase \
serviceCredentialsFile:${{ env.FIREBASE_CREDS_PATH }}
# Only publish bundles to Play Store when `publish-to-play-store` is true while building
# bundles
- name: Publish release bundle to Google Play Store
if: ${{ inputs.publish-to-play-store && matrix.variant == 'aab' }}
env:
PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/authenticator_play_store-creds.json
run: |
bundle exec fastlane publishAuthenticatorReleaseToGooglePlayStore \
serviceCredentialsFile:${{ env.PLAY_STORE_CREDS_FILE }} \

View File

@@ -40,10 +40,10 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
~/.gradle/caches
@@ -53,7 +53,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
${{ github.workspace }}/build-cache
@@ -62,13 +62,13 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
with:
bundler-cache: true
@@ -85,7 +85,7 @@ jobs:
run: bundle exec fastlane assembleDebugApks
- name: Upload test reports on failure
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
if: failure()
with:
name: test-reports
@@ -106,7 +106,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
with:
bundler-cache: true
@@ -157,10 +157,10 @@ jobs:
--name app_play_prod_firebase-creds.json --file ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json --output none
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
~/.gradle/caches
@@ -170,7 +170,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
${{ github.workspace }}/build-cache
@@ -179,20 +179,11 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Update app CI Build info
run: |
./scripts/update_app_ci_build_info.sh \
$GITHUB_REPOSITORY \
$GITHUB_REF_NAME \
$GITHUB_SHA \
$GITHUB_RUN_ID \
$GITHUB_RUN_ATTEMPT
- name: Increment version
run: |
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
@@ -253,78 +244,78 @@ jobs:
- name: Upload release Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.aab
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab
if-no-files-found: error
- name: Upload beta Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.beta.aab
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab
if-no-files-found: error
- name: Upload release .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.apk
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk
if-no-files-found: error
- name: Upload beta .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.beta.apk
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.beta.apk
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk
if-no-files-found: error
# When building variants other than 'prod'
- name: Upload debug .apk artifact
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.apk
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk
if-no-files-found: error
- name: Create checksum for release .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
run: |
sha256sum "app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk" \
sha256sum "app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk" \
> ./com.x8bit.bitwarden.apk-sha256.txt
- name: Create checksum for beta .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
run: |
sha256sum "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.beta.apk" \
sha256sum "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk" \
> ./com.x8bit.bitwarden.beta.apk-sha256.txt
- name: Create checksum for release .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
run: |
sha256sum "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab" \
sha256sum "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab" \
> ./com.x8bit.bitwarden.aab-sha256.txt
- name: Create checksum for beta .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
run: |
sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab" \
sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab" \
> ./com.x8bit.bitwarden.beta.aab-sha256.txt
- name: Create checksum for Debug .apk artifact
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
run: |
sha256sum "app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.apk" \
sha256sum "app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk" \
> ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
- name: Upload .apk SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.apk-sha256.txt
path: ./com.x8bit.bitwarden.apk-sha256.txt
@@ -332,7 +323,7 @@ jobs:
- name: Upload .apk SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.beta.apk-sha256.txt
path: ./com.x8bit.bitwarden.beta.apk-sha256.txt
@@ -340,7 +331,7 @@ jobs:
- name: Upload .aab SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.aab-sha256.txt
path: ./com.x8bit.bitwarden.aab-sha256.txt
@@ -348,7 +339,7 @@ jobs:
- name: Upload .aab SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.beta.aab-sha256.txt
path: ./com.x8bit.bitwarden.beta.aab-sha256.txt
@@ -356,7 +347,7 @@ jobs:
- name: Upload .apk SHA file for debug
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
@@ -405,7 +396,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
with:
bundler-cache: true
@@ -442,10 +433,10 @@ jobs:
--name app_fdroid_firebase-creds.json --file ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json --output none
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
~/.gradle/caches
@@ -455,7 +446,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
${{ github.workspace }}/build-cache
@@ -464,20 +455,11 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Update app CI Build info
run: |
./scripts/update_app_ci_build_info.sh \
$GITHUB_REPOSITORY \
$GITHUB_REF_NAME \
$GITHUB_SHA \
$GITHUB_RUN_ID \
$GITHUB_RUN_ATTEMPT
# Start from 11000 to prevent collisions with mobile build version codes
- name: Increment version
run: |
@@ -515,38 +497,38 @@ jobs:
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden-fdroid.apk
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk
if-no-files-found: error
- name: Create checksum for F-Droid artifact
run: |
sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk" \
sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk" \
> ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
- name: Upload F-Droid SHA file
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden-fdroid.apk-sha256.txt
path: ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
if-no-files-found: error
- name: Upload F-Droid Beta .apk artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.beta-fdroid.apk
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.apk
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden-fdroid-beta.apk
if-no-files-found: error
- name: Create checksum for F-Droid Beta artifact
run: |
sha256sum "app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.apk" \
sha256sum "app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden-fdroid-beta.apk" \
> ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
- name: Upload F-Droid Beta SHA file
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
path: ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt

View File

@@ -1,98 +0,0 @@
name: Cron / Sync Google Privileged Browsers List
on:
schedule:
# Run weekly on Monday at 00:00 UTC
- cron: '0 0 * * 1'
workflow_dispatch:
env:
SOURCE_URL: https://www.gstatic.com/gpm-passkeys-privileged-apps/apps.json
GOOGLE_FILE: app/src/main/assets/fido2_privileged_google.json
COMMUNITY_FILE: app/src/main/assets/fido2_privileged_community.json
jobs:
sync-privileged-browsers:
name: Sync Google Privileged Browsers List
runs-on: ubuntu-24.04
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
- name: Download Google Privileged Browsers List
run: curl -s $SOURCE_URL -o $GOOGLE_FILE
- name: Check for changes
id: check-changes
run: |
if git diff --quiet -- $GOOGLE_FILE; then
echo "👀 No changes detected, skipping..."
echo "has_changes=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "👀 Changes detected, validating fido2_privileged_google.json..."
python .github/scripts/validate-json/validate_json.py validate $GOOGLE_FILE
if [ $? -ne 0 ]; then
echo "::error::JSON validation failed for $GOOGLE_FILE"
exit 1
fi
echo "👀 fido2_privileged_google.json is valid, checking for duplicates..."
# Check for duplicates between Google and Community files
python .github/scripts/validate-json/validate_json.py duplicates $GOOGLE_FILE $COMMUNITY_FILE duplicates.txt
if [ -f duplicates.txt ]; then
echo "::warning::Duplicate package names found between Google and Community files."
echo "duplicates_found=true" >> $GITHUB_OUTPUT
else
echo "✅ No duplicate package names found between Google and Community files"
echo "duplicates_found=false" >> $GITHUB_OUTPUT
fi
- name: Create branch and commit
if: steps.check-changes.outputs.has_changes == 'true'
run: |
echo "👀 Committing fido2_privileged_google.json..."
BRANCH_NAME="cron-sync-privileged-browsers/$GITHUB_RUN_NUMBER-sync"
git config user.name "GitHub Actions Bot"
git config user.email "actions@github.com"
git checkout -b $BRANCH_NAME
git add $GOOGLE_FILE
git commit -m "Update Google privileged browsers list"
git push origin $BRANCH_NAME
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
echo "🌱 Branch created: $BRANCH_NAME"
- name: Create Pull Request
if: steps.check-changes.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DUPLICATES_FOUND: ${{ steps.check-changes.outputs.duplicates_found }}
BASE_PR_URL: ${{ github.server_url }}/${{ github.repository }}/pull/
run: |
PR_BODY="Updates the Google privileged browsers list with the latest data from $SOURCE_URL"
if [ "$DUPLICATES_FOUND" = "true" ]; then
PR_BODY="$PR_BODY\n\n> [!WARNING]\n> :suspect: The following package(s) appear in both Google and Community files:"
while IFS= read -r line; do
PR_BODY="$PR_BODY\n> - $line"
done < duplicates.txt
fi
# Use echo -e to interpret escape sequences and pipe to gh pr create
PR_URL=$(echo -e "$PR_BODY" | gh pr create \
--title "Update Google privileged browsers list" \
--body-file - \
--base main \
--head $BRANCH_NAME \
--label "automated-pr" \
--label "t:ci")

View File

@@ -1,56 +0,0 @@
name: Crowdin Sync - Authenticator
on:
workflow_dispatch:
inputs: {}
schedule:
- cron: '0 0 * * 5'
jobs:
crowdin-sync:
name: Autosync
runs-on: ubuntu-24.04
env:
_CROWDIN_PROJECT_ID: "673718"
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Log in to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Download translations
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
with:
config: crowdin-bwa.yml
upload_sources: false
upload_translations: false
download_translations: true
github_user_name: "bitwarden-devops-bot"
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync
create_pull_request: true
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}

View File

@@ -2,7 +2,7 @@ name: Crowdin Sync
on:
workflow_dispatch:
inputs: {}
inputs: { }
schedule:
- cron: '0 0 * * 5'
@@ -28,17 +28,10 @@ jobs:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Generate GH App token
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
id: app-token
with:
app-id: ${{ secrets.BW_GHAPP_ID }}
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Download translations
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
uses: crowdin/github-action@2d540f18b0a416b1fbf2ee5be35841bd380fc1da # v2.3.0
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
with:
config: crowdin.yml

View File

@@ -1,30 +0,0 @@
name: Crowdin Push - Authenticator
on:
workflow_dispatch:
push:
branches:
- "main"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_VERSION: 17
jobs:
crowdin-push:
name: Crowdin Push
runs-on: ubuntu-24.04
env:
_CROWDIN_PROJECT_ID: "673718"
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Upload sources
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
with:
config: crowdin-bwa.yml
upload_sources: true
upload_translations: false

View File

@@ -23,13 +23,13 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
uses: bitwarden/gh-actions/get-keyvault-secrets@2bd1450c2cdb2a8ac886232b8589696f22794229 # v0.2.0
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token"
- name: Upload sources
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
uses: crowdin/github-action@2d540f18b0a416b1fbf2ee5be35841bd380fc1da # v2.3.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@@ -11,7 +11,7 @@ on:
description: 'Version Number - E.g. "123456"'
required: true
type: string
artifact-run-id:
artifact_run_id:
description: 'GitHub Action Run ID containing artifacts'
required: true
type: string
@@ -22,8 +22,7 @@ on:
prerelease:
description: 'Mark as pre-release'
type: boolean
default: true
make-latest:
make_latest:
description: 'Set as the latest release'
type: boolean
branch-protection-type:
@@ -37,7 +36,6 @@ env:
ARTIFACTS_PATH: artifacts
jobs:
create-release:
name: Create GitHub Release
runs-on: ubuntu-24.04
permissions:
contents: write
@@ -45,7 +43,7 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
@@ -53,7 +51,7 @@ jobs:
id: get_release_branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
BRANCH_PROTECTION_TYPE: ${{ inputs.branch-protection-type }}
run: |
release_branch=$(gh run view $ARTIFACT_RUN_ID --json headBranch -q .headBranch)
@@ -83,7 +81,7 @@ jobs:
- name: Download artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
run: |
gh run download $ARTIFACT_RUN_ID -D $ARTIFACTS_PATH
file_count=$(find $ARTIFACTS_PATH -type f | wc -l)
@@ -95,13 +93,13 @@ jobs:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
with:
tag_name: "v${{ inputs.version-name }}"
name: "${{ inputs.version-name }} (${{ inputs.version-number }})"
tag_name: ${{ inputs.version-name }}
name: "v${{ inputs.version-name }} (${{ inputs.version-number }})"
prerelease: ${{ inputs.prerelease }}
draft: ${{ inputs.draft }}
make_latest: ${{ inputs.make-latest }}
make_latest: ${{ inputs.make_latest }}
target_commitish: ${{ steps.get_release_branch.outputs.release_branch }}
generate_release_notes: true
files: |
@@ -112,7 +110,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_ID: ${{ steps.create_release.outputs.id }}
RELEASE_URL: ${{ steps.create_release.outputs.url }}
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
run: |
# Get current release body
current_body=$(gh api /repos/${{ github.repository }}/releases/$RELEASE_ID --jq .body)

View File

@@ -10,23 +10,26 @@ on:
options:
- RC
- Hotfix
rc_prefix_date:
description: 'RC - Prefix with date. E.g. 2024.11-rc1'
type: boolean
default: true
jobs:
create-release-branch:
name: Create Release Branch
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
- name: Create RC Branch
if: inputs.release_type == 'RC'
env:
RC_PREFIX_DATE: "true" # replace with input if needed
RC_PREFIX_DATE: ${{ inputs.rc_prefix_date }}
run: |
if [ "$RC_PREFIX_DATE" = "true" ]; then
current_date=$(date +'%Y.%m')
@@ -42,17 +45,12 @@ jobs:
- name: Create Hotfix Branch
if: inputs.release_type == 'Hotfix'
run: |
latest_tag=$(git tag -l --sort=-creatordate | head -n 1)
latest_tag=$(git describe --tags --abbrev=0)
if [ -z "$latest_tag" ]; then
echo "::error::No tags found in the repository"
exit 1
fi
branch_name="release/hotfix-${latest_tag}"
echo "🌿 branch name: $branch_name"
if git show-ref --verify --quiet "refs/remotes/origin/$branch_name"; then
echo "# :fire: :warning: Hotfix branch already exists: ${branch_name}" >> $GITHUB_STEP_SUMMARY
exit 0
fi
git switch -c $branch_name $latest_tag
git push origin $branch_name
echo "# :fire: Hotfix branch: ${branch_name}" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,60 +0,0 @@
name: Scan Protected Branches On Push
on:
workflow_dispatch:
push:
branches:
- "main"
jobs:
sast:
name: SAST scan
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
with:
project_name: ${{ github.repository }}
cx_tenant: ${{ secrets.CHECKMARX_TENANT }}
base_uri: https://ast.checkmarx.net/
cx_client_id: ${{ secrets.CHECKMARX_CLIENT_ID }}
cx_client_secret: ${{ secrets.CHECKMARX_SECRET }}
additional_params: |
--report-format sarif \
--filter "state=TO_VERIFY;PROPOSED_NOT_EXPLOITABLE;CONFIRMED;URGENT" \
--output-path .
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
with:
sarif_file: cx_result.sarif
quality:
name: Quality scan
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Scan with SonarCloud
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.organization=${{ github.repository_owner }}
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}

View File

@@ -1,9 +1,16 @@
name: Scan Pull Requests
name: Scan
on:
workflow_dispatch:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
pull_request_target:
types: [opened, synchronize]
merge_group:
types: [checks_requested]
jobs:
check-run:
@@ -26,7 +33,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
uses: checkmarx/ast-github-action@03a90e7253dadd7e2fff55f5dfbce647b39040a1 # 2.0.37
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
@@ -41,11 +48,9 @@ jobs:
--output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
uses: github/codeql-action/upload-sarif@9278e421667d5d90a2839487a482448c4ec7df4d # v3.27.2
with:
sarif_file: cx_result.sarif
sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
quality:
name: Quality scan
@@ -63,11 +68,11 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with SonarCloud
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
uses: sonarsource/sonarcloud-github-action@383f7e52eae3ab0510c3cb0e7d9d150bbaeab838 # v3.1.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >
-Dsonar.organization=${{ github.repository_owner }}
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}

View File

@@ -6,34 +6,42 @@ on:
- "main"
- "rc"
- "hotfix-rc"
pull_request:
pull_request_target:
types: [opened, synchronize]
merge_group:
type: [checks_requested]
workflow_dispatch:
env:
_JAVA_VERSION: 17
_GITHUB_ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_VERSION: 17
jobs:
check-run:
name: Check PR run
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
test:
name: Test
runs-on: ubuntu-24.04
needs: check-run
permissions:
contents: read
issues: write
packages: read
pull-requests: write
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
- name: Cache Gradle files
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
~/.gradle/caches
@@ -43,7 +51,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
${{ github.workspace }}/build-cache
@@ -52,15 +60,15 @@ jobs:
${{ runner.os }}-build-
- name: Configure Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
with:
bundler-cache: true
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
distribution: "temurin"
java-version: ${{ env._JAVA_VERSION }}
java-version: ${{ env.JAVA_VERSION }}
- name: Install Fastlane
run: |
@@ -69,48 +77,19 @@ jobs:
bundle install --jobs 4 --retry 3
- name: Build and test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Used in settings.gradle.kts to download the SDK from GitHub Maven Packages
run: |
bundle exec fastlane check
- name: Upload test reports
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
if: always()
- name: Upload test reports on failure
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
if: failure()
with:
name: test-reports
path: |
build/reports/kover/reportMergedCoverage.xml
app/build/reports/tests/
authenticator/build/reports/tests/
authenticatorbridge/build/reports/tests/
core/build/reports/tests/
data/build/reports/tests/
network/build/reports/tests/
ui/build/reports/tests/
path: app/build/reports/tests/
- name: Upload to codecov.io
id: upload-to-codecov
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
if: github.event_name == 'push' || github.event_name == 'pull_request'
continue-on-error: true
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
with:
os: linux
files: build/reports/kover/reportMergedCoverage.xml
fail_ci_if_error: true
disable_search: true
- name: Comment PR if tests failed
if: steps.upload-to-codecov.outcome == 'failure' && (github.event_name == 'push' || github.event_name == 'pull_request')
file: app/build/reports/kover/reportStandardDebug.xml
env:
PR_NUMBER: ${{ github.event.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_ACTOR: ${{ github.triggering_actor }}
run: |
echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY
echo "> Uploading code coverage report failed. Please check the \"Upload to codecov.io\" step of \"Process Test Reports\" job for more details." >> $GITHUB_STEP_SUMMARY
if [ ! -z "$PR_NUMBER" ]; then
message=$'> [!WARNING]\n> @'$RUN_ACTOR' Uploading code coverage report failed. Please check the "Upload to codecov.io" step of [Process Test Reports job]('$_GITHUB_ACTION_RUN_URL') for more details.'
gh pr comment --repo $GITHUB_REPOSITORY $PR_NUMBER --body "$message"
fi
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

10
.gitignore vendored
View File

@@ -25,13 +25,5 @@ user.properties
# Secrets
/keystores/*.jks
/app/src/standardBeta/google-services.json
/app/src/standardDebug/google-services.json
/app/src/standardRelease/google-services.json
/authenticator/src/google-services.json
# Python
.python-version
__pycache__/
# Generated by .github/scripts/validate-json/validate-json.py
duplicates.txt

View File

@@ -1 +0,0 @@
npx lint-staged

View File

@@ -9,23 +9,21 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.2)
aws-partitions (1.1084.0)
aws-sdk-core (3.222.1)
aws-eventstream (1.3.0)
aws-partitions (1.1003.0)
aws-sdk-core (3.212.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (1.95.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.183.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-s3 (1.170.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-sigv4 (1.10.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
@@ -34,9 +32,9 @@ GEM
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
date (3.4.1)
date (3.4.0)
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)
@@ -61,8 +59,8 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
@@ -70,8 +68,8 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.227.1)
fastimage (2.3.1)
fastlane (2.225.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -111,9 +109,9 @@ GEM
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-firebase_app_distribution (0.10.0)
fastlane-plugin-firebase_app_distribution (0.9.1)
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
fastlane-sirp (1.0.0)
@@ -139,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)
@@ -160,26 +158,23 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
http-cookie (1.0.7)
domain_name (~> 0.5)
httpclient (2.9.0)
mutex_m
httpclient (2.8.3)
jmespath (1.6.2)
json (2.10.2)
jwt (2.10.1)
json (2.8.1)
jwt (2.9.3)
base64
logger (1.7.0)
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)
optparse (0.6.0)
os (1.1.4)
plist (3.7.2)
plist (3.7.1)
public_suffix (6.0.1)
rake (13.2.1)
representable (3.2.0)
@@ -187,10 +182,10 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.4.1)
rouge (3.28.0)
rexml (3.3.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
rubyzip (2.3.2)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
@@ -221,8 +216,8 @@ GEM
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
@@ -238,4 +233,4 @@ RUBY VERSION
ruby 3.3.1p55
BUNDLED WITH
2.6.6
2.5.9

View File

@@ -1,61 +0,0 @@
[![Github Workflow build on main](https://github.com/bitwarden/authenticator-android/actions/workflows/build-authenticator.yml/badge.svg?branch=main)](https://github.com/bitwarden/authenticator-android/actions/workflows/build-authenticator.yml?query=branch:main)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
# Bitwarden Authenticator Android App
<a href="https://play.google.com/store/apps/details?id=com.bitwarden.authenticator" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a>
Bitwarden Authenticator allows you easily store and generate two-factor authentication codes on your device. The Bitwarden Authenticator Android application is written in Kotlin.
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/authenticator-android-codes.png" alt="" width="325" height="650" />
## Compatibility
- **Minimum SDK**: 28
- **Target SDK**: 34
- **Device Types Supported**: Phone and Tablet
- **Orientations Supported**: Portrait and Landscape
## Setup
1. Clone the repository:
```sh
$ git clone https://github.com/bitwarden/authenticator-android
```
2. Create a `user.properties` file in the root directory of the project and add the following properties:
- `gitHubToken`: A "classic" Github Personal Access Token (PAT) with the `read:packages` scope (ex: `gitHubToken=gph_xx...xx`). These can be generated by going to the [Github tokens page](https://github.com/settings/tokens). See [the Github Packages user documentation concerning authentication](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#authenticating-to-github-packages) for more details.
3. Setup the code style formatter:
All code must follow the guidelines described in the [Code Style Guidelines document](docs/STYLE_AND_BEST_PRACTICES.md). To aid in adhering to these rules, all contributors should apply `docs/bitwarden-style.xml` as their code style scheme. In IntelliJ / Android Studio:
- Navigate to `Preferences > Editor > Code Style`.
- Hit the `Manage` button next to `Scheme`.
- Select `Import`.
- Find the `bitwarden-style.xml` file in the project's `docs/` directory.
- Import "from" `BitwardenStyle` "to" `BitwardenStyle`.
- Hit `Apply` and `OK` to save the changes and exit Preferences.
Note that in some cases you may need to restart Android Studio for the changes to take effect.
All code should be formatted before submitting a pull request. This can be done manually but it can also be helpful to create a macro with a custom keyboard binding to auto-format when saving. In Android Studio on OS X:
- Select `Edit > Macros > Start Macro Recording`
- Select `Code > Optimize Imports`
- Select `Code > Reformat Code`
- Select `File > Save All`
- Select `Edit > Macros > Stop Macro Recording`
This can then be mapped to a set of keys by navigating to `Android Studio > Preferences` and editing the macro under `Keymap` (ex : shift + command + s).
Please avoid mixing formatting and logical changes in the same commit/PR. When possible, fix any large formatting issues in a separate PR before opening one to make logical changes to the same code. This helps others focus on the meaningful code changes when reviewing the code.
## Contribute
Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.

View File

@@ -58,11 +58,6 @@
The following is a list of all third-party dependencies included as part of the application beyond the standard Android SDK.
- **AndroidX Activity**
- https://developer.android.com/jetpack/androidx/releases/activity
- Purpose: Allows access composable APIs built on top of Activity.
- License: Apache 2.0
- **AndroidX Appcompat**
- https://developer.android.com/jetpack/androidx/releases/appcompat
- Purpose: Allows access to new APIs on older API versions.
@@ -83,7 +78,7 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: Displays webpages with the user's default browser.
- License: Apache 2.0
- **AndroidX Camera**
- **AndroidX CameraX Camera2**
- https://developer.android.com/jetpack/androidx/releases/camera
- Purpose: Display and capture images for barcode scanning.
- License: Apache 2.0
@@ -93,9 +88,9 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: A Kotlin-based declarative UI framework.
- License: Apache 2.0
- **AndroidX Core**
- **AndroidX Core SplashScreen**
- https://developer.android.com/jetpack/androidx/releases/core
- Purpose: Backwards compatible platform features and APIs.
- Purpose: Backwards compatible SplashScreen API implementation.
- License: Apache 2.0
- **AndroidX Credentials**
@@ -108,11 +103,6 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: Lifecycle aware components and tooling.
- License: Apache 2.0
- **AndroidX Navigation**
- https://developer.android.com/jetpack/androidx/releases/navigation
- Purpose: Provides a consistent API for navigating between Android components.
- License: Apache 2.0
- **AndroidX Room**
- https://developer.android.com/jetpack/androidx/releases/room
- Purpose: A convenient SQLite-based persistence layer for Android.
@@ -133,6 +123,16 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: Dependency injection framework.
- License: Apache 2.0
- **Firebase Cloud Messaging**
- https://github.com/firebase/firebase-android-sdk
- Purpose: Allows for push notification support. (**NOTE:** This dependency is not included in builds distributed via F-Droid.)
- License: Apache 2.0
- **Firebase Crashlytics**
- https://github.com/firebase/firebase-android-sdk
- Purpose: SDK for crash and non-fatal error reporting. (**NOTE:** This dependency is not included in builds distributed via F-Droid.)
- License: Apache 2.0
- **Glide**
- https://github.com/bumptech/glide
- Purpose: Image loading and caching.
@@ -153,6 +153,11 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: JSON serialization library for Kotlin.
- License: Apache 2.0
- **kotlinx.serialization converter**
- https://github.com/square/retrofit/tree/trunk/retrofit-converters/kotlinx-serialization
- Purpose: Converter for Retrofit 2 and kotlinx.serialization.
- License: Apache 2.0
- **OkHttp 3**
- https://github.com/square/okhttp
- Purpose: An HTTP client used by the library to intercept and log traffic.
@@ -168,28 +173,16 @@ The following is a list of all third-party dependencies included as part of the
- Purpose: Extensible logging library for Android.
- License: Apache 2.0
- **zxcvbn4j**
- https://github.com/nulab/zxcvbn4j
- Purpose: Password strength estimation.
- License: MIT
- **ZXing**
- https://github.com/zxing/zxing
- Purpose: Barcode scanning and generation.
- License: Apache 2.0
The following is an additional list of third-party dependencies that are only included in the non-F-Droid build variants of the application.
- **Firebase Cloud Messaging**
- https://github.com/firebase/firebase-android-sdk
- Purpose: Allows for push notification support.
- License: Apache 2.0
- **Firebase Crashlytics**
- https://github.com/firebase/firebase-android-sdk
- Purpose: SDK for crash and non-fatal error reporting.
- License: Apache 2.0
- **Google Play Reviews**
- https://developer.android.com/reference/com/google/android/play/core/release-notes
- Purpose: On standard builds provide an interface to add a review for the password manager application in Google Play.
- License: Apache 2.0
### Development Environment Dependencies
The following is a list of additional third-party dependencies used as part of the local development environment. This includes test-related artifacts as well as tools related to code quality and linting. These are not present in the final packaged application.

View File

@@ -1,32 +1,21 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
users safe. If you believe you've found a security issue in our product or service, we encourage you
to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We
welcome working with you to resolve the issue promptly. Thanks in advance!
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or
a third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.
- If you would like to encrypt your report, please use the PGP key with long ID
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
While researching, we'd like to ask you to refrain from:
- Denial of service
- Spamming
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
- Denial of service
- Spamming
- Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers
# We want to help you!
If you have something that you feel is close to exploitation, or if you'd like some information
regarding the internal API, or generally have any questions regarding the app that would help in
your efforts, please email us at https://bitwarden.com/contact and ask for that information. As
stated above, Bitwarden wants to help you find issues, and is more than willing to help.
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
Thank you for helping keep Bitwarden and our users safe!

View File

@@ -1,9 +1,6 @@
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import com.android.utils.cxx.io.removeExtensionIfPresent
import com.google.firebase.crashlytics.buildtools.gradle.tasks.InjectMappingFileIdTask
import com.google.firebase.crashlytics.buildtools.gradle.tasks.UploadMappingFileTask
import com.google.gms.googleservices.GoogleServicesTask
import dagger.hilt.android.plugin.util.capitalize
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.FileInputStream
import java.util.Properties
@@ -13,13 +10,16 @@ plugins {
// Crashlytics is enabled for all builds initially but removed for FDroid builds in gradle and
// standardDebug builds in the merged manifest.
alias(libs.plugins.crashlytics)
alias(libs.plugins.detekt)
alias(libs.plugins.hilt)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose.compiler)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlinx.kover)
alias(libs.plugins.ksp)
alias(libs.plugins.google.services)
alias(libs.plugins.sonarqube)
}
/**
@@ -32,16 +32,6 @@ val userProperties = Properties().apply {
}
}
/**
* Loads CI-specific build properties that are not checked into source control.
*/
val ciProperties = Properties().apply {
val ciPropsFile = File(rootDir, "ci.properties")
if (ciPropsFile.exists()) {
FileInputStream(ciPropsFile).use { load(it) }
}
}
android {
namespace = "com.x8bit.bitwarden"
compileSdk = libs.versions.compileSdk.get().toInt()
@@ -51,7 +41,7 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 1
versionName = "2025.4.0"
versionName = "2024.9.0"
setProperty("archivesBaseName", "com.x8bit.bitwarden")
@@ -61,12 +51,6 @@ android {
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField(
type = "String",
name = "CI_INFO",
value = "${ciProperties.getOrDefault("ci.info", "\"local\"")}",
)
}
androidResources {
@@ -99,10 +83,9 @@ android {
applicationIdSuffix = ".beta"
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += listOf("release")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
"proguard-rules.pro"
)
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
@@ -113,7 +96,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")
@@ -132,39 +115,6 @@ android {
}
}
applicationVariants.all {
val bundlesDir = "${layout.buildDirectory.get()}/outputs/bundle"
outputs
.mapNotNull { it as? BaseVariantOutputImpl }
.forEach { output ->
val fileNameWithoutExtension = when (flavorName) {
"fdroid" -> "$applicationId-$flavorName"
"standard" -> "$applicationId"
else -> output.outputFileName.removeExtensionIfPresent(".apk")
}
// Set the APK output filename.
output.outputFileName = "$fileNameWithoutExtension.apk"
val variantName = name
val renameTaskName = "rename${variantName.capitalize()}AabFiles"
tasks.register(renameTaskName) {
group = "build"
description = "Renames the bundle files for $variantName variant"
doLast {
renameFile(
"$bundlesDir/$variantName/$namespace-$flavorName-${buildType.name}.aab",
"$fileNameWithoutExtension.aab",
)
}
}
// Force renaming task to execute after the variant is built.
tasks
.getByName("bundle${variantName.capitalize()}")
.finalizedBy(renameTaskName)
}
}
compileOptions {
sourceCompatibility(libs.versions.jvmTarget.get())
targetCompatibility(libs.versions.jvmTarget.get())
@@ -178,6 +128,7 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
@Suppress("UnstableApiUsage")
testOptions {
// Required for Robolectric
unitTests.isIncludeAndroidResources = true
@@ -213,11 +164,6 @@ dependencies {
implementation(files("libs/authenticatorbridge-1.0.0-release.aar"))
implementation(project(":core"))
implementation(project(":data"))
implementation(project(":network"))
implementation(project(":ui"))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.autofill)
@@ -229,7 +175,6 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
@@ -254,6 +199,7 @@ dependencies {
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization)
implementation(libs.nulab.zxcvbn4j)
implementation(libs.square.okhttp)
implementation(libs.square.okhttp.logging)
implementation(platform(libs.square.retrofit.bom))
@@ -270,16 +216,9 @@ dependencies {
standardImplementation(libs.google.firebase.cloud.messaging)
standardImplementation(platform(libs.google.firebase.bom))
standardImplementation(libs.google.firebase.crashlytics)
standardImplementation(libs.google.play.review)
// Pull in test fixtures from other modules
testImplementation(testFixtures(project(":data")))
testImplementation(testFixtures(project(":network")))
testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.google.hilt.android.testing)
testImplementation(platform(libs.junit.bom))
testRuntimeOnly(libs.junit.platform.launcher)
testImplementation(libs.junit.junit5)
testImplementation(libs.junit.vintage)
testImplementation(libs.kotlinx.coroutines.test)
@@ -287,39 +226,117 @@ dependencies {
testImplementation(libs.robolectric.robolectric)
testImplementation(libs.square.okhttp.mockwebserver)
testImplementation(libs.square.turbine)
detektPlugins(libs.detekt.detekt.formatting)
detektPlugins(libs.detekt.detekt.rules)
}
detekt {
autoCorrect = true
config.from(files("$rootDir/detekt-config.yml"))
}
kover {
currentProject {
sources {
excludeJava = true
}
}
reports {
filters {
excludes {
androidGeneratedClasses()
annotatedBy(
// Compose previews
"androidx.compose.ui.tooling.preview.Preview",
"androidx.compose.ui.tooling.preview.PreviewScreenSizes",
// Manually excluded classes/files/etc.
"com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage",
)
classes(
// Navigation helpers
"*.*NavigationKt*",
// Composable singletons
"*.*ComposableSingletons*",
// Generated classes related to interfaces with default values
"*.*DefaultImpls*",
// Databases
"*.database.*Database*",
"*.dao.*Dao*",
// Dagger Hilt
"dagger.hilt.*",
"hilt_aggregated_deps.*",
"*_Factory",
"*_Factory\$*",
"*_*Factory",
"*_*Factory\$*",
"*.Hilt_*",
"*_HiltModules",
"*_HiltModules\$*",
"*_Impl",
"*_Impl\$*",
"*_MembersInjector",
)
packages(
// Dependency injection
"*.di",
// Models
"*.model",
// Custom UI components
"com.x8bit.bitwarden.ui.platform.components",
// Theme-related code
"com.x8bit.bitwarden.ui.platform.theme",
)
}
}
}
}
tasks {
getByName("check") {
// Add detekt with type resolution to check
dependsOn("detekt")
}
withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
jvmTarget = libs.versions.jvmTarget.get()
}
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>().configureEach {
jvmTarget = libs.versions.jvmTarget.get()
}
withType<Test> {
useJUnitPlatform()
maxHeapSize = "2g"
maxParallelForks = Runtime.getRuntime().availableProcessors()
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC"
android.sourceSets["main"].res.srcDirs("src/test/res")
}
}
afterEvaluate {
// Disable Fdroid-specific tasks that we want to exclude
val fdroidTasksToDisable = tasks.withType<GoogleServicesTask>() +
val tasks = tasks.withType<GoogleServicesTask>() +
tasks.withType<InjectMappingFileIdTask>() +
tasks.withType<UploadMappingFileTask>()
fdroidTasksToDisable
tasks
.filter { it.name.contains("Fdroid") }
.forEach { it.enabled = false }
}
private fun renameFile(path: String, newName: String) {
val originalFile = File(path)
if (!originalFile.exists()) {
println("File $originalFile does not exist!")
return
}
val newFile = File(originalFile.parentFile, newName)
if (originalFile.renameTo(newFile)) {
println("Renamed $originalFile to $newFile")
} else {
throw RuntimeException("Failed to rename $originalFile to $newFile")
sonar {
properties {
property("sonar.projectKey", "bitwarden_android")
property("sonar.organization", "bitwarden")
property("sonar.host.url", "https://sonarcloud.io")
property("sonar.sources", "app/src/")
property("sonar.tests", "app/src/")
property("sonar.test.inclusions", "app/src/test/")
property("sonar.exclusions", "app/src/test/")
}
}
tasks {
getByName("sonar") {
dependsOn("check")
}
}

View File

@@ -1,12 +0,0 @@
package com.x8bit.bitwarden.ui.platform.manager.review
import android.app.Activity
/**
* No-op implementation of [AppReviewManager] for F-Droid builds.
*/
class AppReviewManagerImpl(
activity: Activity,
) : AppReviewManager {
override fun promptForReview() = Unit
}

View File

@@ -15,7 +15,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<!-- Protect access to AuthenticatorBridgeService using this custom permission.
Note that each build type uses a different value for knownCerts.
@@ -320,11 +320,6 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent>
<!-- To Query Chrome Beta: -->
<package android:name="com.chrome.beta" />
<!-- To Query Chrome Stable: -->
<package android:name="com.android.chrome" />
</queries>
</manifest>

View File

@@ -1,17 +1,5 @@
{
"apps": [
{
"type": "android",
"info": {
"package_name": "io.github.forkmaintainers.iceraven",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:0D:22:37:9F:48:7B:70:A4:F9:F8:BE:C0:17:3C:F9:1A:16:44:F0:8F:93:38:5B:5B:78:2C:E3:76:60:BA:81"
}
]
}
},
{
"type": "android",
"info": {
@@ -36,18 +24,6 @@
]
}
},
{
"type": "android",
"info": {
"package_name": "org.ironfoxoss.ironfox",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C5:E2:91:B5:A5:71:F9:C8:CD:9A:97:99:C2:C9:4E:02:EC:97:03:94:88:93:F2:CA:75:6D:67:B9:42:04:F9:04"
}
]
}
},
{
"type": "android",
"info": {
@@ -59,6 +35,46 @@
}
]
}
},
{
"type": "android",
"info": {
"package_name": "us.spotco.fennec_dos",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "26:0E:0A:49:67:8C:78:B7:0C:02:D6:53:7A:DD:3B:6D:C0:A1:71:71:BB:DE:8C:E7:5F:D4:02:6A:8A:3E:18:D2"
},
{
"build": "release",
"cert_fingerprint_sha256": "FF:81:F5:BE:56:39:65:94:EE:E7:0F:EF:28:32:25:6E:15:21:41:22:E2:BA:9C:ED:D2:60:05:FF:D4:BC:AA:A8"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "us.spotco.mulch",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "26:0E:0A:49:67:8C:78:B7:0C:02:D6:53:7A:DD:3B:6D:C0:A1:71:71:BB:DE:8C:E7:5F:D4:02:6A:8A:3E:18:D2"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.github.forkmaintainers.iceraven",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:0D:22:37:9F:48:7B:70:A4:F9:F8:BE:C0:17:3C:F9:1A:16:44:F0:8F:93:38:5B:5B:78:2C:E3:76:60:BA:81"
}
]
}
}
]
}

View File

@@ -160,78 +160,6 @@
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.fenix",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "50:04:77:90:88:E7:F9:88:D5:BC:5C:C5:F8:79:8F:EB:F4:F8:CD:08:4A:1B:2A:46:EF:D4:C8:EE:4A:EA:F2:11"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.fenix.debug",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.focus.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.focus.nightly",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.klar",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.reference.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "B0:09:90:E3:0F:9D:81:5D:2E:BC:7B:9B:B2:21:CE:47:E5:C9:D5:17:AA:C7:0E:7F:D5:95:B1:E5:3E:9A:4B:14"
}
]
}
},
{
"type": "android",
"info": {
@@ -643,142 +571,6 @@
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.Island",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "D9:C3:39:AC:9C:3A:EE:E1:75:1D:85:8C:35:D9:BA:C5:CC:87:B3:CE:76:30:93:F0:F5:10:64:F5:A2:F6:9B:04"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.IslandCanary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "90:17:13:23:45:6E:6F:39:CB:FD:CF:B2:56:BE:1D:CF:F3:BC:1C:59:8A:15:93:30:E4:97:73:D0:4C:B9:C9:05"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.IslandBeta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "35:31:83:1A:9E:2B:21:1D:E6:AA:C3:69:4B:45:83:6E:56:09:B9:D7:D0:04:C3:1B:21:87:40:FB:77:17:38:D1"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.IslandDev",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.island.intune",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C2:38:24:15:41:20:A0:8F:C3:95:42:AC:D8:2A:E9:24:94:78:80:1E:47:FD:6C:66:2B:18:1C:28:CA:7E:59:4E"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.island.canary.intune",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "1E:16:74:BB:79:EA:09:FB:37:CF:9F:1B:07:1B:1D:51:8D:46:03:0E:D3:EE:F2:C1:4E:AD:93:9E:C6:EE:3A:4C"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.island.beta.intune",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "D2:5E:AD:F6:1C:E6:36:6C:A4:23:A4:7F:C4:DB:9B:8C:9C:8A:35:B4:B0:19:E8:D9:82:FB:D0:8A:D9:DB:49:5A"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "io.island.island.dev.intune",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "net.quetta.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "BE:FE:E7:31:12:6A:A5:6E:7E:FD:AE:AF:5E:F3:FA:EA:44:1C:19:CC:E0:CA:EC:42:6B:65:BB:F8:2C:59:46:80"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "F1:38:00:4F:38:04:51:D4:8A:05:2B:B3:A3:EF:17:24:23:D4:B0:D0:C8:A3:AA:DD:FB:DB:66:30:31:48:EC:A4"
}
]
}
}
]
}
}

View File

@@ -2,7 +2,7 @@ package com.x8bit.bitwarden
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
/**
* An activity to be launched and then immediately closed so that the OS Shade can be collapsed

View File

@@ -4,7 +4,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import dagger.hilt.android.AndroidEntryPoint
/**

View File

@@ -4,8 +4,8 @@ import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

View File

@@ -5,10 +5,10 @@ import android.content.Intent
import android.os.Build
import androidx.annotation.Keep
import androidx.core.app.AppComponentFactory
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.autofill.BitwardenAutofillService
import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService
import com.x8bit.bitwarden.data.autofill.fido2.BitwardenFido2ProviderService
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService
import com.x8bit.bitwarden.data.tiles.BitwardenGeneratorTileService
import com.x8bit.bitwarden.data.tiles.BitwardenVaultTileService

View File

@@ -1,11 +1,11 @@
package com.x8bit.bitwarden
import android.app.Application
import com.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.manager.LogsManager
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConfigManager
import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManager
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject

View File

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

View File

@@ -1,7 +1,6 @@
package com.x8bit.bitwarden
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
@@ -12,41 +11,37 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.getValue
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.bitwarden.core.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
import com.x8bit.bitwarden.data.platform.manager.util.ObserveScreenDataEffect
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
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.settings.appearance.model.AppLanguage
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() {
private val mainViewModel: MainViewModel by viewModels()
@Inject
lateinit var accessibilityActivityManager: AccessibilityActivityManager
@Inject
lateinit var autofillActivityManager: AutofillActivityManager
@@ -62,7 +57,6 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var debugLaunchManager: DebugMenuLaunchManager
@Suppress("LongMethod")
override fun onCreate(savedInstanceState: Bundle?) {
var shouldShowSplashScreen = true
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
@@ -76,10 +70,13 @@ class MainActivity : AppCompatActivity() {
)
}
// Within the app the 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.
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
// Within the app the language will change dynamically and will be managed
// by the OS, but we need to ensure we properly set the language when
// upgrading from older versions that handle this differently.
settingsRepository.appLanguage.localeName?.let { localeName ->
val localeList = LocaleListCompat.forLanguageTags(localeName)
AppCompatDelegate.setApplicationLocales(localeList)
}
setContent {
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
val navController = rememberNavController()
@@ -101,43 +98,15 @@ class MainActivity : AppCompatActivity() {
)
.show()
}
is MainEvent.UpdateAppLocale -> {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(event.localeName),
)
}
is MainEvent.UpdateAppTheme -> {
AppCompatDelegate.setDefaultNightMode(event.osTheme)
}
}
}
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
LocalManagerProvider(featureFlagsState = state.featureFlagsState) {
ObserveScreenDataEffect(
onDataUpdate = remember(mainViewModel) {
{
mainViewModel.trySendAction(
MainAction.ResumeScreenDataReceived(it),
)
}
},
)
LocalManagerProvider {
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 },
)
}
)
}
}
}
@@ -152,33 +121,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
}
mainViewModel.trySendAction(
action = MainAction.AppSpecificLanguageUpdate(
appLanguage = appSpecificLanguage ?: AppLanguage.DEFAULT,
),
)
}
override fun onStop() {
super.onStop()
// In some scenarios on an emulator the Activity can leak when recreated

View File

@@ -4,8 +4,6 @@ import android.content.Intent
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
@@ -15,18 +13,14 @@ import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CreateCredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
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 +28,10 @@ import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticato
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
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.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 +52,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,26 +61,21 @@ 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,
private val savedStateHandle: SavedStateHandle,
private val appResumeManager: AppResumeManager,
private val clock: Clock,
) : BaseViewModel<MainState, MainEvent, MainAction>(
initialState = MainState(
theme = settingsRepository.appTheme,
isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed,
isErrorReportingDialogEnabled = featureFlagManager.getFeatureFlag(
key = FlagKey.MobileErrorReporting,
),
),
) {
private var specialCircumstance: SpecialCircumstance?
@@ -105,12 +93,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) }
@@ -126,11 +108,6 @@ class MainViewModel @Inject constructor(
.appThemeStateFlow
.onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) }
.launchIn(viewModelScope)
settingsRepository
.appLanguageStateFlow
.map { MainEvent.UpdateAppLocale(it.localeName) }
.onEach(::sendEvent)
.launchIn(viewModelScope)
settingsRepository
.isScreenCaptureAllowedStateFlow
@@ -149,7 +126,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 +139,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 +164,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,28 +177,9 @@ 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)
}
}
}
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()
else -> appResumeManager.setResumeScreen(data)
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
}
}
@@ -262,7 +211,6 @@ class MainViewModel @Inject constructor(
private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) {
mutableStateFlow.update { it.copy(theme = action.theme) }
sendEvent(MainEvent.UpdateAppTheme(osTheme = action.theme.osValue))
}
private fun handleVaultUnlockStateChange() {
@@ -309,10 +257,10 @@ class MainViewModel @Inject constructor(
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
val hasVaultShortcut = intent.isMyVaultShortcut
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
val completeRegistrationData = intent.getCompleteRegistrationDataIntentOrNull()
val fido2CreateCredentialRequest = intent.getFido2CreateCredentialRequestOrNull()
val fido2CredentialAssertionRequest = intent.getFido2AssertionRequestOrNull()
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
val fido2AssertCredentialRequest = intent.getFido2AssertionRequestOrNull()
when {
passwordlessRequestData != null -> {
authRepository.activeUserId?.let {
@@ -370,39 +318,28 @@ class MainViewModel @Inject constructor(
)
}
fido2CreateCredentialRequest != null -> {
fido2CredentialRequestData != null -> {
// Set the user's verification status when a new FIDO 2 request is received to force
// explicit verification if the user's vault is unlocked when the request is
// received.
fido2CreateCredentialRequest.providerRequest
.biometricPromptResult
?.isSuccessful
?.let { isVerified -> fido2CredentialManager.isUserVerified = isVerified }
fido2CredentialManager.isUserVerified = false
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CreateCredentialRequest = fido2CreateCredentialRequest,
fido2CredentialRequest = fido2CredentialRequestData,
)
// Switch accounts if the selected user is not the active user.
if (authRepository.activeUserId != null &&
authRepository.activeUserId != fido2CreateCredentialRequest.userId
authRepository.activeUserId != fido2CredentialRequestData.userId
) {
authRepository.switchAccount(fido2CreateCredentialRequest.userId)
authRepository.switchAccount(fido2CredentialRequestData.userId)
}
}
fido2AssertCredentialRequest != null -> {
// If device biometric verification was performed as part of single-tap
// authentication, set the user's verification state to the device result.
// Otherwise, retain the verification state as-is.
fido2AssertCredentialRequest.providerRequest.biometricPromptResult
?.isSuccessful
?.let { isVerified -> fido2CredentialManager.isUserVerified = isVerified }
fido2CredentialAssertionRequest != null -> {
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Assertion(
fido2AssertionRequest = fido2AssertCredentialRequest,
fido2AssertionRequest = fido2CredentialAssertionRequest,
)
}
@@ -485,16 +422,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,17 +443,6 @@ sealed class MainAction {
*/
data object OpenDebugMenu : MainAction()
/**
* Receive event to save the app resume screen
*/
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.
*/
@@ -538,13 +455,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.
*/
@@ -608,18 +518,4 @@ sealed class MainEvent {
* Show a toast with the given [message].
*/
data class ShowToast(val message: Text) : MainEvent()
/**
* Indicates that the app language has been updated.
*/
data class UpdateAppLocale(
val localeName: String?,
) : MainEvent()
/**
* Indicates that the app theme has been updated.
*/
data class UpdateAppTheme(
val osTheme: Int,
) : MainEvent()
}

View File

@@ -1,12 +1,11 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
import com.bitwarden.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.coroutines.flow.Flow
import java.time.Instant
/**
* Primary access point for disk information.
@@ -172,16 +171,6 @@ interface AuthDiskSource {
pendingAuthRequest: PendingAuthRequestJson?,
)
/**
* Gets the biometrics initialization vector for the given [userId].
*/
fun getUserBiometricInitVector(userId: String): ByteArray?
/**
* Stores the biometrics initialization vector for the given [userId].
*/
fun storeUserBiometricInitVector(userId: String, iv: ByteArray?)
/**
* Gets the biometrics key for the given [userId].
*/
@@ -339,17 +328,7 @@ interface AuthDiskSource {
fun storeShowImportLogins(userId: String, showImportLogins: Boolean?)
/**
* Emits updates that track [getShowImportLogins]. This will replay the last known value.
* Emits updates that track [getShowImportLogins]. This will replay the last known value,
*/
fun getShowImportLoginsFlow(userId: String): Flow<Boolean?>
/**
* Gets the last lock timestamp for the given [userId].
*/
fun getLastLockTimestamp(userId: String): Instant?
/**
* Stores the last lock timestamp for the given [userId].
*/
fun storeLastLockTimestamp(userId: String, lastLockTimestamp: Instant?)
}

View File

@@ -1,27 +1,26 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
import android.content.SharedPreferences
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.decodeFromStringOrNull
import com.bitwarden.data.datasource.disk.BaseEncryptedDiskSource
import com.bitwarden.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant
import java.util.UUID
// These keys should be encrypted
private const val ACCOUNT_TOKENS_KEY = "accountTokens"
private const val AUTHENTICATOR_SYNC_SYMMETRIC_KEY = "authenticatorSyncSymmetric"
private const val AUTHENTICATOR_SYNC_UNLOCK_KEY = "authenticatorSyncUnlock"
private const val BIOMETRICS_INIT_VECTOR_KEY = "biometricInitializationVector"
private const val BIOMETRICS_UNLOCK_KEY = "userKeyBiometricUnlock"
private const val USER_AUTO_UNLOCK_KEY_KEY = "userKeyAutoUnlock"
private const val DEVICE_KEY_KEY = "deviceKey"
@@ -47,7 +46,6 @@ private const val TDE_LOGIN_COMPLETE = "tdeLoginComplete"
private const val USES_KEY_CONNECTOR = "usesKeyConnector"
private const val ONBOARDING_STATUS_KEY = "onboardingStatus"
private const val SHOW_IMPORT_LOGINS_KEY = "showImportLogins"
private const val LAST_LOCK_TIMESTAMP = "lastLockTimestamp"
/**
* Primary implementation of [AuthDiskSource].
@@ -144,7 +142,6 @@ class AuthDiskSourceImpl(
storePrivateKey(userId = userId, privateKey = null)
storeOrganizationKeys(userId = userId, organizationKeys = null)
storeOrganizations(userId = userId, organizations = null)
storeUserBiometricInitVector(userId = userId, iv = null)
storeUserBiometricUnlockKey(userId = userId, biometricsKey = null)
storeMasterPasswordHash(userId = userId, passwordHash = null)
storePolicies(userId = userId, policies = null)
@@ -153,7 +150,6 @@ class AuthDiskSourceImpl(
storeIsTdeLoginComplete(userId = userId, isTdeLoginComplete = null)
storeAuthenticatorSyncUnlockKey(userId = userId, authenticatorSyncUnlockKey = null)
storeShowImportLogins(userId = userId, showImportLogins = null)
storeLastLockTimestamp(userId = userId, lastLockTimestamp = null)
// Do not remove the DeviceKey or PendingAuthRequest on logout, these are persisted
// indefinitely unless the TDE flow explicitly removes them.
@@ -281,17 +277,6 @@ class AuthDiskSourceImpl(
)
}
override fun getUserBiometricInitVector(userId: String): ByteArray? =
getEncryptedString(key = BIOMETRICS_INIT_VECTOR_KEY.appendIdentifier(userId))
?.toByteArray(Charsets.ISO_8859_1)
override fun storeUserBiometricInitVector(userId: String, iv: ByteArray?) {
putEncryptedString(
key = BIOMETRICS_INIT_VECTOR_KEY.appendIdentifier(userId),
value = iv?.toString(Charsets.ISO_8859_1),
)
}
override fun getUserBiometricUnlockKey(userId: String): String? =
getEncryptedString(key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId))
@@ -486,19 +471,6 @@ class AuthDiskSourceImpl(
getMutableShowImportLoginsFlow(userId)
.onSubscription { emit(getShowImportLogins(userId)) }
override fun getLastLockTimestamp(userId: String): Instant? {
return getLong(key = LAST_LOCK_TIMESTAMP.appendIdentifier(userId))?.let {
Instant.ofEpochMilli(it)
}
}
override fun storeLastLockTimestamp(userId: String, lastLockTimestamp: Instant?) {
putLong(
key = LAST_LOCK_TIMESTAMP.appendIdentifier(userId),
value = lastLockTimestamp?.toEpochMilli(),
)
}
private fun generateAndStoreUniqueAppId(): String =
UUID
.randomUUID()

View File

@@ -1,10 +1,10 @@
package com.x8bit.bitwarden.data.auth.datasource.disk.di
import android.content.SharedPreferences
import com.bitwarden.data.datasource.disk.di.EncryptedPreferences
import com.bitwarden.data.datasource.disk.di.UnencryptedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.di.EncryptedPreferences
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
import dagger.Module
import dagger.Provides

View File

@@ -1,13 +1,11 @@
package com.x8bit.bitwarden.data.auth.datasource.disk.model
import com.bitwarden.network.model.KdfTypeJson
import com.bitwarden.network.model.UserDecryptionOptionsJson
import kotlinx.serialization.Contextual
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
import java.time.ZonedDateTime
/**
* Represents the current account information for a given user.
@@ -37,7 +35,6 @@ data class AccountJson(
* @property userId The ID of the user.
* @property email The user's email address.
* @property isEmailVerified Whether or not the user's email is verified.
* @property isTwoFactorEnabled If the profile has two factor authentication enabled.
* @property name The user's name (if applicable).
* @property stamp The account's security stamp (if applicable).
* @property organizationId The ID of the associated organization (if applicable).
@@ -49,7 +46,6 @@ data class AccountJson(
* @property kdfMemory The amount of memory to use when calculating a password hash (MB).
* @property kdfParallelism The number of threads to use when calculating a password hash.
* @property userDecryptionOptions The options available to a user for decryption.
* @property creationDate The creation date of the account.
*/
@OptIn(ExperimentalSerializationApi::class)
@Serializable
@@ -63,9 +59,6 @@ data class AccountJson(
@SerialName("emailVerified")
val isEmailVerified: Boolean?,
@SerialName("isTwoFactorEnabled")
val isTwoFactorEnabled: Boolean?,
@SerialName("name")
val name: String?,
@@ -99,10 +92,6 @@ data class AccountJson(
@SerialName("userDecryptionOptions")
@JsonNames("accountDecryptionOptions")
val userDecryptionOptions: UserDecryptionOptionsJson?,
@SerialName("creationDate")
@Contextual
val creationDate: ZonedDateTime?,
)
/**

View File

@@ -7,7 +7,6 @@ import kotlinx.serialization.Serializable
* Represents URLs for various Bitwarden domains.
*
* @property base The overall base URL.
* @property keyUri A Uri containing the alias and host of the key used for mutual TLS.
* @property api Separate base URL for the "/api" domain (if applicable).
* @property identity Separate base URL for the "/identity" domain (if applicable).
* @property icon Separate base URL for the icon domain (if applicable).
@@ -20,9 +19,6 @@ data class EnvironmentUrlDataJson(
@SerialName("base")
val base: String,
@SerialName("keyUri")
val keyUri: String? = null,
@SerialName("api")
val api: String? = null,
@@ -55,7 +51,6 @@ data class EnvironmentUrlDataJson(
*/
val DEFAULT_LEGACY_US: EnvironmentUrlDataJson = EnvironmentUrlDataJson(
base = "https://vault.bitwarden.com",
keyUri = null,
api = "https://api.bitwarden.com",
identity = "https://identity.bitwarden.com",
icon = "https://icons.bitwarden.net",
@@ -76,7 +71,6 @@ data class EnvironmentUrlDataJson(
*/
val DEFAULT_LEGACY_EU: EnvironmentUrlDataJson = EnvironmentUrlDataJson(
base = "https://vault.bitwarden.eu",
keyUri = null,
api = "https://api.bitwarden.eu",
identity = "https://identity.bitwarden.eu",
icon = "https://icons.bitwarden.eu",

View File

@@ -1,11 +1,11 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.CreateAccountKeysRequest
import com.bitwarden.network.model.DeleteAccountRequestJson
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.ResetPasswordRequestJson
import com.bitwarden.network.model.SetPasswordRequestJson
import com.bitwarden.network.model.VerifyOtpRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.CreateAccountKeysRequest
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.HTTP
import retrofit2.http.POST

View File

@@ -1,9 +1,9 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.AuthRequestRequestJson
import com.bitwarden.network.model.AuthRequestUpdateRequestJson
import com.bitwarden.network.model.AuthRequestsResponseJson
import com.bitwarden.network.model.NetworkResult
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header

View File

@@ -1,9 +1,9 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import androidx.annotation.Keep
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.TrustedDeviceKeysRequestJson
import com.bitwarden.network.model.TrustedDeviceKeysResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.PUT
import retrofit2.http.Path

View File

@@ -1,8 +1,8 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import androidx.annotation.Keep
import com.bitwarden.network.model.KeyConnectorMasterKeyRequestJson
import com.bitwarden.network.model.NetworkResult
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url

View File

@@ -1,9 +1,9 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.OrganizationAutoEnrollStatusResponseJson
import com.bitwarden.network.model.OrganizationKeysResponseJson
import com.bitwarden.network.model.OrganizationResetPasswordEnrollRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PUT

View File

@@ -1,6 +1,6 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Path

View File

@@ -1,11 +1,10 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.KeyConnectorKeyRequestJson
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.PasswordHintRequestJson
import com.bitwarden.network.model.ResendEmailRequestJson
import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson
import com.bitwarden.network.util.HEADER_KEY_AUTHORIZATION
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.POST
@@ -29,9 +28,4 @@ interface UnauthenticatedAccountsApi {
@Body body: KeyConnectorKeyRequestJson,
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
): NetworkResult<Unit>
@POST("/accounts/resend-new-device-otp")
suspend fun resendNewDeviceOtp(
@Body body: ResendNewDeviceOtpRequestJson,
): NetworkResult<Unit>
}

View File

@@ -1,8 +1,8 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.AuthRequestRequestJson
import com.bitwarden.network.model.AuthRequestsResponseJson
import com.bitwarden.network.model.NetworkResult
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header

View File

@@ -1,6 +1,6 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.GET
import retrofit2.http.Header

View File

@@ -1,16 +1,16 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.GetTokenResponseJson
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.PreLoginRequestJson
import com.bitwarden.network.model.PreLoginResponseJson
import com.bitwarden.network.model.PrevalidateSsoResponseJson
import com.bitwarden.network.model.RefreshTokenResponseJson
import com.bitwarden.network.model.RegisterFinishRequestJson
import com.bitwarden.network.model.RegisterRequestJson
import com.bitwarden.network.model.RegisterResponseJson
import com.bitwarden.network.model.SendVerificationEmailRequestJson
import com.bitwarden.network.model.VerifyEmailTokenRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import kotlinx.serialization.json.JsonPrimitive
import retrofit2.Call
import retrofit2.http.Body
@@ -47,13 +47,12 @@ interface UnauthenticatedIdentityApi {
@Field(value = "twoFactorProvider") twoFactorMethod: String?,
@Field(value = "twoFactorRemember") twoFactorRemember: String?,
@Field(value = "authRequest") authRequestId: String?,
@Field(value = "newDeviceOtp") newDeviceOtp: String?,
): NetworkResult<GetTokenResponseJson.Success>
@GET("/sso/prevalidate")
suspend fun prevalidateSso(
@Query("domainHint") organizationIdentifier: String,
): NetworkResult<PrevalidateSsoResponseJson.Success>
): NetworkResult<PrevalidateSsoResponseJson>
/**
* This call needs to be synchronous so we need it to return a [Call] directly. The identity

View File

@@ -1,10 +1,10 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import androidx.annotation.Keep
import com.bitwarden.network.model.KeyConnectorMasterKeyRequestJson
import com.bitwarden.network.model.KeyConnectorMasterKeyResponseJson
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.util.HEADER_KEY_AUTHORIZATION
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header

View File

@@ -1,10 +1,10 @@
package com.bitwarden.network.api
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.bitwarden.network.model.NetworkResult
import com.bitwarden.network.model.OrganizationDomainSsoDetailsRequestJson
import com.bitwarden.network.model.OrganizationDomainSsoDetailsResponseJson
import com.bitwarden.network.model.VerifiedOrganizationDomainSsoDetailsRequest
import com.bitwarden.network.model.VerifiedOrganizationDomainSsoDetailsResponse
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body
import retrofit2.http.POST
@@ -21,7 +21,7 @@ interface UnauthenticatedOrganizationApi {
): NetworkResult<OrganizationDomainSsoDetailsResponseJson>
/**
* Checks for the verified organization domains of an email for SSO purposes.
* Checks for the verfied organization domains of an email for SSO purposes.
*/
@POST("/organizations/domain/sso/verified")
suspend fun getVerifiedOrganizationDomainsByEmail(

View File

@@ -1,13 +1,13 @@
package com.x8bit.bitwarden.data.auth.datasource.network.di
import com.bitwarden.network.service.AccountsService
import com.bitwarden.network.service.AccountsServiceImpl
import com.bitwarden.network.service.AuthRequestsService
import com.bitwarden.network.service.AuthRequestsServiceImpl
import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.DevicesServiceImpl
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.HaveIBeenPwnedServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,7 +1,7 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import androidx.annotation.Keep
import com.bitwarden.core.data.serializer.BaseEnumeratedIntSerializer
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -21,7 +21,5 @@ enum class AuthRequestTypeJson {
}
@Keep
private class AuthRequestTypeSerializer : BaseEnumeratedIntSerializer<AuthRequestTypeJson>(
className = "AuthRequestTypeJson",
values = AuthRequestTypeJson.entries.toTypedArray(),
)
private class AuthRequestTypeSerializer :
BaseEnumeratedIntSerializer<AuthRequestTypeJson>(AuthRequestTypeJson.entries.toTypedArray())

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -23,15 +23,6 @@ sealed class DeleteAccountResponseJson {
@Serializable
data class Invalid(
@SerialName("validationErrors")
private val validationErrors: Map<String, List<String?>>?,
) : DeleteAccountResponseJson() {
/**
* A human readable error message.
*/
val message: String?
get() = validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
}
val validationErrors: Map<String, List<String?>>?,
) : DeleteAccountResponseJson()
}

View File

@@ -1,9 +1,7 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
import kotlinx.serialization.json.JsonObject
/**
@@ -94,56 +92,41 @@ sealed class GetTokenResponseJson {
/**
* Models json body of an invalid request.
*
* This model supports older versions of the error response model that used lower-case keys.
*/
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class Invalid(
@JsonNames("errorModel")
@SerialName("ErrorModel")
private val errorModel: ErrorModel?,
val errorModel: ErrorModel?,
@SerialName("errorModel")
val legacyErrorModel: LegacyErrorModel?,
) : GetTokenResponseJson() {
/**
* The error message returned from the server, or null.
*/
val errorMessage: String? get() = errorModel?.errorMessage
/**
* The type of invalid responses that can be received.
*/
sealed class InvalidType {
/**
* Represents an invalid response indicating that a new device verification is required.
*/
data object NewDeviceVerification : InvalidType()
/**
* Represents generic invalid response
*/
data object GenericInvalid : InvalidType()
}
val invalidType: InvalidType
get() = if (errorMessage?.lowercase() == "new device verification required") {
InvalidType.NewDeviceVerification
} else {
InvalidType.GenericInvalid
}
val errorMessage: String?
get() = errorModel?.errorMessage ?: legacyErrorModel?.errorMessage
/**
* The error body of an invalid request containing a message.
*
* This model supports older versions of the error response model that used lower-case
* keys.
*/
@Serializable
data class ErrorModel(
@JsonNames("message")
@SerialName("Message")
val errorMessage: String,
)
/**
* The legacy error body of an invalid request containing a message.
*
* This model is used to support older versions of the error response model that used
* lower-case keys.
*/
@Serializable
data class LegacyErrorModel(
@SerialName("message")
val errorMessage: String,
)
}
/**

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,7 +1,7 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import androidx.annotation.Keep
import com.bitwarden.core.data.serializer.BaseEnumeratedIntSerializer
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -18,7 +18,5 @@ enum class KdfTypeJson {
}
@Keep
private class KdfTypeSerializer : BaseEnumeratedIntSerializer<KdfTypeJson>(
className = "KdfTypeJson",
values = KdfTypeJson.entries.toTypedArray(),
)
private class KdfTypeSerializer :
BaseEnumeratedIntSerializer<KdfTypeJson>(KdfTypeJson.entries.toTypedArray())

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,6 +1,6 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import com.bitwarden.network.serializer.BaseSurrogateSerializer
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseSurrogateSerializer
import kotlinx.serialization.Serializable
/**

View File

@@ -0,0 +1,12 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Response body from the SSO prevalidate request.
*/
@Serializable
data class PrevalidateSsoResponseJson(
@SerialName("token") val token: String?,
)

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,5 +1,6 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequestJson.Keys
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,5 +1,6 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson.Keys
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,9 +1,7 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
/**
* Models response bodies for the register request.
@@ -52,24 +50,20 @@ sealed class RegisterResponseJson {
* The values in the array should be used for display to the user, since the keys tend to come
* back as nonsense. (eg: empty string key)
*/
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class Invalid(
@JsonNames("message")
@SerialName("Message")
@SerialName("message")
private val invalidMessage: String? = null,
@SerialName("Message")
private val errorMessage: String? = null,
@SerialName("validationErrors")
private val validationErrors: Map<String, List<String>>?,
val validationErrors: Map<String, List<String>>?,
) : RegisterResponseJson() {
/**
* A generic error message.
*/
val message: String?
get() = validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
?: invalidMessage
val message: String? get() = invalidMessage ?: errorMessage
}
}

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -1,9 +1,7 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
/**
* The response body for sending a verification email.
@@ -28,24 +26,20 @@ sealed class SendVerificationEmailResponseJson {
* The values in the array should be used for display to the user, since the keys tend to come
* back as nonsense. (eg: empty string key)
*/
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class Invalid(
@JsonNames("message")
@SerialName("message")
private val invalidMessage: String? = null,
@SerialName("Message")
private val errorMessage: String? = null,
@SerialName("validationErrors")
private val validationErrors: Map<String, List<String>>?,
val validationErrors: Map<String, List<String>>?,
) : SendVerificationEmailResponseJson() {
/**
* A generic error message.
*/
val message: String?
get() = validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
?: errorMessage
val message: String? get() = invalidMessage ?: errorMessage
}
}

View File

@@ -1,6 +1,6 @@
package com.bitwarden.network.model
package com.x8bit.bitwarden.data.auth.datasource.network.model
import com.bitwarden.network.model.RegisterRequestJson.Keys
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson.Keys
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

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