Compare commits

..

633 Commits

Author SHA1 Message Date
Álison Fernandes
01188c21b2 Update BWA secret names 2025-02-26 22:14:56 +00:00
Álison Fernandes
dca7284230 Merge remote-tracking branch 'bwa-android/main' into bwa-monorepo
# Conflicts:
#	.checkmarx/config.yml
#	.github/CODEOWNERS
#	.github/ISSUE_TEMPLATE/bug.yml
#	.github/ISSUE_TEMPLATE/config.yml
#	.github/renovate.json
#	.github/workflows/build-authenticator.yml
#	.github/workflows/crowdin-pull-authenticator.yml
#	.github/workflows/crowdin-push-authenticator.yml
#	.github/workflows/scan-authenticator.yml
#	.github/workflows/test-authenticator.yml
#	.gitignore
#	Gemfile
#	Gemfile.lock
#	README.md
#	build.gradle.kts
#	fastlane/Fastfile
#	gradle.properties
#	gradle/libs.versions.toml
#	gradle/wrapper/gradle-wrapper.properties
#	gradlew.bat
#	settings.gradle.kts
2025-02-26 22:13:24 +00:00
Dave Severns
2893c3871f PM-18636 Hide coach mark card if any login ciphers exist (#4787)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
Co-authored-by: Philip Cappelli <phil@livefront.com>
2025-02-26 18:48:10 +00:00
André Bispo
d04ac5e672 [PM-18451] Elevated privileges do not exempt from remove pin unlock policy (#4791) 2025-02-26 16:48:56 +00:00
Álison Fernandes
55e03565a6 [PM-18655] sync with bitwarden/template (#4795) 2025-02-26 15:24:00 +00:00
Álison Fernandes
768f7a3fd9 [PM-16534] Monorepo prep - Update checkmarx, renovate and gitignore (#4794) 2025-02-26 15:23:01 +00:00
Álison Fernandes
1d02737093 [PM-18651] Add Bitwarden Authenticator issue template and update existing bug template (#4792) 2025-02-26 15:22:44 +00:00
Álison Fernandes
fb792a668b [PM-17412] Retrieve firebase files from container and remove ui-test version ref (#357)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-02-26 08:59:37 -05:00
Álison Fernandes
64da29ffaa [PM-16534] Merge authenticator-android libs and fastlane files (#4782) 2025-02-25 22:49:26 +00:00
Bernd Schoolmann
675cbb7c4f [PM-15149] Remove ssh feature flag (#4761) 2025-02-25 22:33:36 +00:00
Patrick Honkonen
6b63218839 [PM-17142] Remove ExampleInstrumentedTest (#358) 2025-02-25 22:21:41 +00:00
André Bispo
30a1bba796 [PM-15873] Fix PTR in sends listing page (#4784) 2025-02-25 19:06:45 +00:00
David Perez
c2d9e4858b Standardize all compose tests with theme and back handling (#4779) 2025-02-25 12:34:10 -06:00
André Bispo
d8e42083b7 [PM-18451] Validate remove pin policy against user privileges (#4774)
Co-authored-by: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com>
2025-02-25 16:14:27 +00:00
André Bispo
ac7fbfd129 [PM-15873] Fix PTR on item listing page (#4778) 2025-02-25 16:14:18 +00:00
Peter Dave Hello
25dfa74bdf [BWA-153] Fix the GitHub Workflow badge in README.md (#351) 2025-02-25 10:33:29 -05:00
renovate[bot]
85a98e86c4 [deps]: Lock file maintenance (#352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 10:33:03 -05:00
Álison Fernandes
078d80d3f6 [PM-17412] Rename debug keystore (#356) 2025-02-25 10:30:14 -05:00
David Perez
00eb78f02e Simplify the RegisterResponseJson error models (#4776) 2025-02-24 15:32:41 -06:00
David Perez
eadfac5ea8 Simplify error response models (#4775) 2025-02-24 14:52:00 -06:00
David Perez
a651d9b1fc Update Kotlin and ksp to latest versions (#4773) 2025-02-24 13:13:14 -06:00
Álison Fernandes
6308aed34a [PM-17412] Update libs.versions.toml ahead of repo merge (#349) 2025-02-24 12:22:27 -06:00
Dave Severns
011d637f7c PM-18129 add authenticator illustration for 2fa screen (#4763) 2025-02-24 09:51:54 -05:00
David Perez
0b03d2c0d5 Update hilt v2.55 (#4769) 2025-02-23 11:28:40 -06:00
David Perez
bb7e4061cc Update firebase (#4765) 2025-02-22 13:20:53 -06:00
David Perez
d1308cb936 Update Compose BOM to 2025.02.00 (#4764) 2025-02-21 15:12:35 -06:00
Dave Severns
892f817b2a PM-18315 add UI when 3pa is available for each chrome channel which s… (#4758) 2025-02-21 15:09:57 -05:00
David Perez
86e5789f30 Add NetworkErrorCode enum to make error parsing more readable (#4762) 2025-02-21 13:02:47 -06:00
David Perez
80bd1bfde2 PM-18496: Propagate prevalidateSso API error message (#4759) 2025-02-21 08:34:18 -06:00
bw-ghapp[bot]
4943df24b3 Autosync Crowdin Translations (#4760)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-02-21 08:32:04 -06:00
Dave Severns
b7464b87d9 PM-18314 & PM-18450 Check for Chrome browser 3rd party autofill. (#4752) 2025-02-20 15:59:39 -05:00
David Perez
3fb7904a36 PM-18480 Update BitwardenSwitch padding (#4757) 2025-02-20 14:57:13 -06:00
David Perez
9d9f9e3e72 PM-18121: Use correct cipher type for edit screen (#4755) 2025-02-20 12:11:49 -06:00
André Bispo
a061cbb1d3 [PM-15873] Add delay to PTR to remove the spinning wheel (#4750) 2025-02-20 17:49:00 +00:00
David Perez
ad03f8c996 PM-18452: Update BitwardenMultiSelectionButton (#4754) 2025-02-20 11:12:42 -06:00
André Bispo
aac2345a64 [PM-18545] Hide section when no unlock option are available (#4751) 2025-02-20 14:30:39 +00:00
David Perez
61c48bf673 PM-18121, PM-18294: Add, Edit, and View cipher screens require cipher type for top app bar title (#4746) 2025-02-19 14:56:55 -05:00
Phil Cappelli
77b631b021 PM-18292 - Swap "Notes" title with "Additional Options" (#4749) 2025-02-19 14:35:59 -05:00
Phil Cappelli
c59a28a9df PM 18033 - Only show Setup Unlock and Autofill Setup onboarding steps after new account creation (#4748) 2025-02-19 14:35:46 -05:00
Dave Severns
1349165156 PM-18421 Remove adding a folder option from within folder view. (#4747) 2025-02-19 12:55:54 -05:00
Patrick Honkonen
65cc0a0dd8 [PM-3553] Support SimpleLogin self hosted servers (#4723) 2025-02-19 10:20:28 -05:00
Dave Severns
0ba67f5887 PM-18032 Adding a new folder while adding or editing an item. (#4731) 2025-02-18 17:25:51 -05:00
David Perez
87f64d7aba PM-18370: Update space between label and tooltip (#4744) 2025-02-18 15:03:26 -06:00
David Perez
063003b4aa PM-18410: Remove cipher type dropdown from add item screen (#4743) 2025-02-18 14:27:36 -06:00
André Bispo
f2eb524da4 [PM-18281] Change cipher key encryption flag default value to false (#4742) 2025-02-18 20:07:45 +00:00
David Perez
e929ca8a7d PM-18370: Allow selecting type of cipher to add from collection list (#4741) 2025-02-18 10:59:31 -06:00
André Bispo
acc5e30b7a [PM-17882] Cannot select autofill after creating new login (#4728) 2025-02-18 15:13:29 +00:00
David Perez
26d4a397a6 Update to gradle plugin 8.8.1 (#4740) 2025-02-18 08:35:18 -06:00
Álison Fernandes
ba9a0d8884 [PM-17412] Renamed crowdin.yml to crowdin-bwa.yml (#348) 2025-02-18 09:33:05 -05:00
Patrick Honkonen
bb2e98eac6 [PM-14936] Add AnonAddy self-hosted server URL support (#4708) 2025-02-18 08:57:47 -05:00
Álison Fernandes
133d8548e8 [PM-18384] Add placeholder workflows for the Authenticator (#4736) 2025-02-17 22:17:49 +00:00
David Perez
9f19a99eb9 PM-18275: Add totp tooltip on view item screen (#4732) 2025-02-17 15:44:55 -06:00
David Perez
22931bbd38 Improve the error messaging when an enum is not parsed correctly (#4730) 2025-02-17 15:03:05 -06:00
David Perez
359dedc9d7 Fix minor typo (#4734) 2025-02-17 20:37:06 +00:00
Phil Cappelli
be416f87de PM-18292 - Update section headers for all items to align with V3 Design (#4729) 2025-02-17 14:09:51 -05:00
Phil Cappelli
6a3a534304 PM-17566 - Authenticator Sync: In Dark theme, the Action card in the Authenticator is too dark (#347) 2025-02-17 13:42:42 -05:00
Patrick Honkonen
09254a2285 Update Bitwarden SDK and use sdk-android-temp (#346) 2025-02-14 15:44:04 +00:00
aj-rosado
f0d6599eb3 [PM-8223] Add new device verification when no OTP (#4712) 2025-02-14 14:03:15 +00:00
bw-ghapp[bot]
d7a50b092b Autosync Crowdin Translations (#4725) 2025-02-14 09:01:57 -05:00
Dave Severns
9f616b67c9 PM-18123 Update the reset password screen. (#4719) 2025-02-13 16:07:17 -05:00
Dave Severns
1198de2a74 PM-17766 add new strings for ssh keys empty item (#4722) 2025-02-13 16:07:01 -05:00
Patrick Honkonen
3db7b17e3a [PM-3553] Add SimpleLogin self-hosted alias feature flag (#4715) 2025-02-13 12:51:07 -05:00
Álison Fernandes
631be3fca5 [PM-16534] Update gradle invocations to specify app module (#4720)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-02-12 20:01:02 -05:00
Patrick Honkonen
e8e6040318 Rename CI workflows for Authenticator (#333)
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
2025-02-13 00:12:21 +00:00
Patrick Honkonen
5790397a27 [PM-16534] Update gradle invocations to specify authenticator module (#344) 2025-02-12 23:41:31 +00:00
Álison Fernandes
8b98e8f461 [PM-16534] Update fastfile check lane (#342)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-02-12 18:21:13 -05:00
Álison Fernandes
f3482bd3e2 [PM-16534] Update test lane name (#343) 2025-02-12 18:18:28 -05:00
Álison Fernandes
14ef7351bd [PM-16534] Fix fastfile typo (#341) 2025-02-12 23:00:34 +00:00
Patrick Honkonen
0820061466 [PM-11886] Update handling of unprivileged apps and improve error messaging (#4694) 2025-02-12 17:52:12 -05:00
Álison Fernandes
c5616b9e38 [PM-16534] Fix Fastfile lanes (#340) 2025-02-12 22:51:13 +00:00
Patrick Honkonen
0538becf7f Update output filenames (#335)
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
2025-02-12 22:31:20 +00:00
Patrick Honkonen
2ae3d5e100 Rename Fastfile lanes to be Authenticator-specific (#334)
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
2025-02-12 17:24:45 -05:00
André Bispo
2aa371a972 [PM-18050] Remove pin policy (#4718) 2025-02-12 22:16:35 +00:00
Patrick Honkonen
72ac8d3699 Rename app package to authenticator (#305)
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
2025-02-12 22:13:07 +00:00
Dave Severns
00f30c922d PM-18058 and PM-18059 Choose which type of vault item to add from Vault screen and inside a Folder. (#4703) 2025-02-12 09:21:15 -05:00
Dave Severns
a68b370cba PM-18083 Ensure segmented buttons on generator fill entire width evenly. (#4713) 2025-02-11 18:08:35 -05:00
Patrick Honkonen
60a6b2a545 [PM-14936] Add AnonAddySelfHostAlias feature flag (#4711) 2025-02-11 18:06:45 -05:00
Patrick Honkonen
2c63c1ab94 [PM-14936] Move prefixHttpsIfNecessaryOrNull to StringExtensions (#4709) 2025-02-11 18:06:17 -05:00
André Bispo
fdc92712c4 [PM-17368] After cut, update text and clear selection. (#4714) 2025-02-11 23:02:08 +00:00
Phil Cappelli
4eb4d5f256 PM-17566 - Authenticator Sync: In Dark theme, the Action card in the Authenticator is too dark (#337) 2025-02-11 11:41:54 -05:00
Álison Fernandes
6fdb39026f [PM-18193] Remove scan.yml from Merge Queue status check and update test.yml report job skip (#4710) 2025-02-10 23:31:01 +00:00
David Perez
ac6c90eb7a PM-18013: Update the View Item screens (#4699) 2025-02-10 13:59:44 -05:00
Phil Cappelli
eb3bc838d6 PM-17838 - Add help button for authenticator key (#4697) 2025-02-10 18:58:26 +00:00
Dave Severns
30e882d7d1 PM-17769 add thinner version of vault icon for the settings (#4704) 2025-02-10 13:58:02 -05:00
Patrick Honkonen
3adc80fdf8 [PM-18082] Force incognito keyboard on input fields (#4700) 2025-02-10 13:38:29 -05:00
bw-ghapp[bot]
e70b21c951 Autosync Crowdin Translations (#4702) 2025-02-10 13:37:58 -05:00
rohm1
571b8368e1 [PM-16157] Support self-host servers using TLS with Client Authentication (mTLS) (#4486) 2025-02-10 18:33:28 +00:00
Patrick Honkonen
fd26472f71 [PM-17405] Configure mutual-tls FlagKey as remotely configured (#4701) 2025-02-10 13:09:16 -05:00
bw-ghapp[bot]
6ffe9df353 Autosync Crowdin Translations (#336) 2025-02-07 09:34:52 -05:00
David Perez
7e0fc07a7c PM-17968: Create unique secret keys per user and handle decoding error (#4696) 2025-02-06 15:38:12 +00:00
David Perez
d27ab1d31a Apply formatter to the entire app (#4698) 2025-02-06 14:52:09 +00:00
Matt Andreko
7b9cbb7bef Enabled SonarQube scanning for PRs (#332) 2025-02-05 13:06:08 -05:00
Dave Severns
a1a3d55656 PM-17848 update copy on generator modal (#4691) 2025-02-05 14:32:33 +00:00
Patrick Honkonen
c672bff18c [PM-17694] Only update FIDO2 user verification status during single-tap sign-in (#4680) 2025-02-05 13:46:52 +00:00
ifernandezdiaz
7ab5972893 QA-1056: Adding testTag to Stepper value text (#4690) 2025-02-05 13:45:45 +00:00
ifernandezdiaz
13aa3251d5 QA-1061: Adding testTags for new WelcomePage (#4695) 2025-02-05 13:45:40 +00:00
Dave Severns
a5f6864512 PM-17847 update text style for add account label (#4693) 2025-02-05 13:11:19 +00:00
David Perez
c3506c1c25 PM-17841: Hide additional options behind expandable section (#4687) 2025-02-04 22:43:27 +00:00
Lucas
1710b563c0 [PM-16862] FIDO2 Community: Remove DivestOS-developed browsers (#4533) 2025-02-04 22:30:18 +00:00
Matt Andreko
01b646afa7 Enabled SonarQube scanning for PRs (#4692) 2025-02-04 20:22:47 +00:00
Álison Fernandes
445bd90f67 [PM-17962] Exclude generated Hilt .java files from code coverage (#4689) 2025-02-04 17:37:38 +00:00
David Perez
de7416d96e PM-17958: Remove language supporting text (#4688) 2025-02-04 17:22:23 +00:00
Álison Fernandes
5e29df5f30 [PM-10515] fix: Missing build information in About screen version copy (#4679) 2025-02-04 15:58:27 +00:00
David Perez
5f48d7bebd PM-17958: Update appearance text (#4686) 2025-02-04 15:52:25 +00:00
Álison Fernandes
b342f21cbb [PM-17939] Restrict test.yml coverage upload to On Push and Pull Request triggers (#4681) 2025-02-03 23:03:55 +00:00
Patrick Honkonen
4b3ec1d918 [PM-17930] Remove default arguments from CoachMarkHighlight (#4677) 2025-02-03 22:18:16 +00:00
Dave Severns
5f52c49a68 PM-17769 Icons added to the settings menu rows (#4673) 2025-02-03 20:18:32 +00:00
David Perez
efe1475fbf PM-17851: Update manual code entry screen (#4676) 2025-02-03 19:40:27 +00:00
Patrick Honkonen
ffe5ddb4a6 [PM-16136] Update Bitwarden SDK (#4675) 2025-02-03 18:38:35 +00:00
Dave Severns
6f82251332 PM-17766 Updated empty states for grouped types and send types. (#4667) 2025-02-03 18:33:40 +00:00
David Perez
76e780f813 PM-17839 PM-17827 PM-17824 PM-17832 PM-17836 PM-17840: VaultAddEditScreen and VaultMoveToOrganizationScreen (#4668) 2025-02-03 17:19:25 +00:00
Patrick Honkonen
161d8517a4 [PM-9535] Show toast when copying values prior to Android 13 (#4654) 2025-02-03 16:59:44 +00:00
Dave Severns
0786ab98a7 PM-17910 Prevent back events from system when coach mark tour is in progress. (#4674) 2025-02-03 16:46:41 +00:00
bw-ghapp[bot]
3d12c98969 Autosync Crowdin Translations (#330) 2025-02-03 11:24:51 -05:00
Phil Cappelli
07fe6e53ea PM-17845 PM-17718 - Enable Remote Configuration for the import flow & Rename Authenticator Sync Feature Flag Name (#4666) 2025-02-03 15:00:14 +00:00
Phil Cappelli
2bbadf8726 BWA-144 - Consolidate feature flags for sync between the Password Manager Authenticator (#331) 2025-02-03 10:00:05 -05:00
Dave Severns
f004e10d41 PM-17801 and PM-17791 updates some string resources related to coach marks and Autofill casing. (#4664) 2025-02-03 14:34:14 +00:00
David Perez
38c9d6cfbc Fix vault favorites spacing (#4665) 2025-01-31 18:15:26 +00:00
David Perez
62d26491d5 Update TOTP coachmark layout (#4663) 2025-01-31 17:52:57 +00:00
Dave Severns
925db01b44 PM-17797 and PM-17798 set onboarding features to be remotely configurable (#4662) 2025-01-31 15:49:56 +00:00
Dave Severns
2b79cc9a17 PM-17765 & PM-17767 Adjust spacing in vault screen and adjust account switcher icon size and minimum row height (#4661) 2025-01-31 15:23:10 +00:00
David Perez
f141465b41 Simplify modifier extensions (#4657) 2025-01-31 15:03:03 +00:00
Phil Cappelli
ecc5d4ce30 PM-16861 - Update Behavior When Tapping Same Generator Tab Already Viewing (#4653) 2025-01-31 15:02:17 +00:00
bw-ghapp[bot]
de558bf94d Autosync Crowdin Translations (#4656)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-01-31 00:42:43 +00:00
David Perez
9b3bb32c87 PM-17376, PM-17380, PM-17385: Update card padding and layout (#4652) 2025-01-30 21:21:14 +00:00
Dave Severns
6f5cb0df96 PM-17764 update the email verification to completely match new design. (#4655) 2025-01-30 21:04:11 +00:00
mpbw2
2195d07f78 [PM-13351] Prevent editing of TOTP key in 'can edit except passwords' collection (#4583) 2025-01-30 20:12:28 +00:00
Phil Cappelli
c09b02f1d0 PM-17384 PM-17386 - Create Account Design Audit (#4647) 2025-01-29 21:33:15 +00:00
Dave Severns
76c7f8c41d PM-17388 Update existing and v3 email verification screen to match design audit (#4645) 2025-01-29 20:41:44 +00:00
David Perez
6a1f37b243 PM-17721: Update app dropdown menus (#4646) 2025-01-29 20:26:50 +00:00
Dave Severns
590fc21820 PM-16625 PM-16626 PM-16627 Coach marks 4-6 on generator screen (#4640) 2025-01-29 17:57:56 +00:00
Patrick Honkonen
4575fa96c1 [deps]: Update AndroidX library versions (#324) 2025-01-29 11:31:22 -05:00
David Perez
1a7ddbef7a PM-17205: Check accessibility service status on start up (#4644) 2025-01-29 15:46:07 +00:00
Patrick Honkonen
555d30645f [deps]: Update Hilt (#325) 2025-01-29 09:41:00 -06:00
Patrick Honkonen
80d04fdf9c [deps]: Update ProtoBufJava (#326) 2025-01-29 09:40:20 -06:00
Patrick Honkonen
4ea92223c0 [deps]: Update Google Guava (#327) 2025-01-29 09:22:38 -06:00
Patrick Honkonen
f8e41caaf2 [deps]: Update Firebase BoM (#328) 2025-01-29 09:19:48 -06:00
Patrick Honkonen
3963623407 [deps]: Update JUnit 5 (#323) 2025-01-29 09:18:54 -06:00
renovate[bot]
bbb2d8dc05 [deps]: Update androidx.compose:compose-bom to v2025 (#319) 2025-01-29 10:10:42 -05:00
bw-ghapp[bot]
db035bbd7d Autosync Crowdin Translations (#322) 2025-01-29 09:23:48 -05:00
David Perez
69f33ddca9 PM-17680: Overwrite the expiration date to the deletion date (#4642) 2025-01-28 22:20:47 +00:00
David Perez
2eca28d571 PM-17684: Update the cursor color throughout the app (#4643) 2025-01-28 22:17:30 +00:00
David Perez
d9ac445149 Fix minor formatting issues in Authenticator Bridge Readme (#4641) 2025-01-28 20:55:07 +00:00
aj-rosado
fe06bf48e7 [PM-13626] Remember last opened view for 5 minutes (#4574)
Signed-off-by: Andre Rosado <arosado@bitwarden.com>
Co-authored-by: Dave Severns <dseverns@livefront.com>
2025-01-28 19:41:11 +00:00
Robyn MacCallum
3f1f9983e3 Update SingleTapPasskeyAuthentication and SingleTapPassskeyCreation to be remote flags (#4639) 2025-01-28 18:37:12 +00:00
Dave Severns
a681402956 PM-16622 PM-16623 and PM-16624 Add the first three coach marks to the generator tour (#4613) 2025-01-28 18:33:19 +00:00
Phil Cappelli
3c7262d2b3 PM-17382 - Update “Logging in as…” text and link style on log in screen (#4638) 2025-01-28 17:18:43 +00:00
Dave Severns
7a25aafc23 PM-17650 Implement custom tool tip state to prevent tool tips from dismissing. (#4637) 2025-01-28 15:20:15 +00:00
Dave Severns
b2c4fbb593 Back port Reverts PM-14995 (#4633) (#4635) 2025-01-28 14:49:33 +00:00
David Perez
e2dec1f2fe PM-17638: Add card background for the manual totp screen (#4634) 2025-01-27 22:39:26 +00:00
David Perez
5f1ef71b3b PM-17378: Update remember me text (#4624) 2025-01-24 22:47:39 +00:00
David Perez
b3550bc933 Fix the login test tag (#4625) 2025-01-24 22:43:25 +00:00
David Perez
09b898d655 PM-17377: Update text, formatting, and style for the environment selector (#4623) 2025-01-24 21:34:42 +00:00
Patrick Honkonen
92b92403c6 [PM-17531] Add dialog for client certificate import (#4622) 2025-01-24 21:08:21 +00:00
Phil Cappelli
b84d393208 [PM-17374] [PM-17375] [PM-17379] - LandingScreen Design Audit (#4611) 2025-01-24 21:02:21 +00:00
Patrick Honkonen
464f8de5f5 [PM-17424] Implement KeyManager for handling private keys (#4608) 2025-01-24 20:55:33 +00:00
David Perez
3a6db38172 Update BitwardenPasswordField TestTags (#4621) 2025-01-24 17:08:33 +00:00
Dave Severns
dbefcfe342 PM-16630 PM-16621 Add logins action card and add explore generator card to be able to trigger coach marks (#4616) 2025-01-24 16:19:13 +00:00
bw-ghapp[bot]
e2d2d7fd7d Autosync Crowdin Translations (#4619)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-01-24 14:08:06 +00:00
David Perez
31ccf490ae PM-17409: Allow nullable labels text fields (#4617) 2025-01-23 23:07:23 +00:00
David Perez
50ae0902f0 PM-14179: Update internal placement of test tags for the BitwardenTextField (#4612) 2025-01-23 22:10:18 +00:00
David Perez
873f416fec PM-15804, PM-17130: Add logic to monitor when the screen on state to ensure the vault locks properly (#4610) 2025-01-23 21:58:11 +00:00
David Perez
70e72da404 PM-17410: Update password hint font (#4614) 2025-01-23 21:54:07 +00:00
Phil Cappelli
d573ce6e10 PM-17074-PM-17802 - Send Screen Design Updates (#4604) 2025-01-23 17:23:24 +00:00
Patrick Honkonen
e9159cc8c1 [PM-15906] Implement single tap passkey flows (#4547) 2025-01-23 16:38:36 +00:00
David Perez
bf60f8f9e3 PM-17404: Set app delegate on theme change (#4605) 2025-01-22 20:02:18 +00:00
Patrick Honkonen
2787edbf45 [PM-17405] Add mutual TLS feature flag (#4606) 2025-01-22 14:41:57 +00:00
SymphonicDeviation
bb5aeaaf15 [PM-17099] Re-Sort Quetta Browser Alphabetically (#4562) 2025-01-21 21:52:08 +00:00
ifernandezdiaz
f68f44615c [QA-980] Adding missing testTags for Custom fields (#4569) 2025-01-21 21:47:07 +00:00
renovate[bot]
66ff5942e4 [deps]: Update gh minor (#4591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 19:48:48 +00:00
renovate[bot]
a11d8eff4a [deps]: Update kotlin (#309) 2025-01-21 19:46:30 +00:00
Patrick Honkonen
b233505c24 [PM-16391] Update Gradle and Android Gradle Plugin (#321) 2025-01-21 14:41:30 -05:00
renovate[bot]
ad564f98ef [deps]: Update gh minor (#318) 2025-01-21 19:38:39 +00:00
renovate[bot]
d488e7d5dd [deps]: Lock file maintenance (#320) 2025-01-21 14:36:35 -05:00
bw-ghapp[bot]
2d66fb5702 Autosync Crowdin Translations (#317) 2025-01-21 14:19:45 -05:00
Dave Severns
2b94e01c56 PM-16631 Applying CoachMarkContainer to the AddLoginItem content. (#4571) 2025-01-21 16:31:45 +00:00
David Perez
08e51fde98 Update the AndroidX Activity library to 1.10.0 (#4599) 2025-01-20 22:17:00 +00:00
David Perez
e25743e3f0 Update Firebase to the latest version v33.8.0 (#4598) 2025-01-20 20:28:28 +00:00
Phil Cappelli
055c598491 PM-16850-PM-16851-PM-16852 - Updating full screen loading indicator (#4581) 2025-01-20 19:24:55 +00:00
renovate[bot]
a185a94d56 [deps]: Update androidx.compose:compose-bom to v2025 (#4593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 16:02:49 +00:00
renovate[bot]
f1ee9c89c0 [deps]: Lock file maintenance (#4594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 15:03:00 +00:00
renovate[bot]
5b81c4dc7c [deps]: Update org.jetbrains.kotlinx.kover to v0.9.1 (#4592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 15:02:34 +00:00
David Perez
e54d9ab5a9 Remove outer box on EnvironmentSelector (#4577) 2025-01-17 15:07:11 +00:00
David Perez
f9fc61ecf5 Add spacer between type and name when creating a login cipher (#4579) 2025-01-17 15:06:54 +00:00
bw-ghapp[bot]
a0eadc282b Autosync Crowdin Translations (#4580)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-01-17 15:05:32 +00:00
David Perez
412649ed9e Add correct card padding to BitwardenHiddenPasswordField (#4576) 2025-01-16 16:52:42 +00:00
André Bispo
be4014962c [PM-16905] Add back button to new device notice (#4570) 2025-01-16 15:05:36 +00:00
David Perez
5840bcdf85 PM-14179: Create and apply card style to UI (#4567) 2025-01-15 22:51:14 +00:00
ifernandezdiaz
2672f426dc [QA-969] Adding missing testTag for Folder Name textfield (#4564) 2025-01-15 18:59:37 +00:00
ifernandezdiaz
20f9421ea4 [QA-968] Adding missing testTag for MP hint email field (#4565) 2025-01-15 18:59:00 +00:00
Álison Fernandes
c1bb58cf17 [PM-17119] Add domains to network config (#4568) 2025-01-15 16:54:20 +00:00
Matt Andreko
f92d748de3 Update SonarQube GitHub Action (#316) 2025-01-14 09:48:46 -05:00
Phil Cappelli
21597ba746 PM-16830 - Update global loading screen component to new reskinned version (#4558) 2025-01-13 19:09:29 +00:00
Patrick Honkonen
efbb959ecc [PM-17011] Move network managers to network package (#4559) 2025-01-13 18:59:21 +00:00
David Perez
b128d5de0a Update AGP to v8.8.0 (#4557) 2025-01-13 16:08:44 +00:00
Álison Fernandes
41d9e96406 [PM-16827] Only report coverage when tests pass (#4550) 2025-01-10 20:20:58 +00:00
renovate[bot]
ac6bb35d76 [deps]: Update gh minor (#4551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 19:26:56 +00:00
Patrick Honkonen
1316882ef4 [BWA-141] Use files instead of file in codecov action (#315) 2025-01-10 10:55:07 -05:00
renovate[bot]
7fdb669786 [deps]: Update sonarsource/sonarcloud-github-action action to v4 (#297) 2025-01-10 15:37:03 +00:00
Phil Cappelli
f595cee7f6 BWA-106 - Import process failures with limited error feedback (#313) 2025-01-10 10:32:34 -05:00
bw-ghapp[bot]
43844739a2 Autosync Crowdin Translations (#314) 2025-01-10 09:54:23 -05:00
SymphonicDeviation
a3096c04f1 [PM-14240] Add Quetta Browser to Privileged Apps (#4189) 2025-01-10 14:49:24 +00:00
Patrick Honkonen
0684011bda [PM-15918] Update bitwarden SDK (#4529) 2025-01-10 14:39:04 +00:00
bw-ghapp[bot]
eef0b1cbbb Autosync Crowdin Translations (#4546)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-01-10 14:30:06 +00:00
celenityy
542d2ad1e9 [PM-16870] Add support for IronFox (#4534)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-01-10 14:24:47 +00:00
André Bispo
e63a549485 [PM-16808] Add question mark to copy (#4544) (#4545) 2025-01-10 09:44:08 +00:00
André Bispo
3e552564dc [PM-16670] Add check for 2fa status #4542 (#4543) 2025-01-09 22:44:06 +00:00
Patrick Honkonen
0a8d1fa0f5 [PM-9439] Use passkey icon for items with FIDO2 credentials in search results (#4541) 2025-01-09 21:10:02 +00:00
David Perez
f2c87d1f66 PM-15356: Resolve biometrics bypass (#4448) 2025-01-09 20:45:14 +00:00
mpbw2
2f2db1a03f [PM-13349] Hide Edit option in cipher list item overflow when editing not permitted (#4539) 2025-01-09 20:29:08 +00:00
Álison Fernandes
0493710cb4 [PM-16827] Fix test.yml sdk package access and refactor test jobs (#4538) 2025-01-09 20:17:04 +00:00
aj-rosado
b5d73c98fe [PM-16695] Learn more new device verification (#4527)
Co-authored-by: André Bispo <abispo@bitwarden.com>
2025-01-09 14:40:19 +00:00
André Bispo
6b6e95aa3f [PM-16670] Force app to sync after 2FA notice (#4525) (#4536) 2025-01-09 00:25:38 +00:00
André Bispo
f35ee76c95 [PM-16809] Fix remind me later date (#4526) (#4535) 2025-01-08 22:57:10 +00:00
David Perez
bb66150b5c PM-14179: Update generator screen copy button (#4530) 2025-01-08 19:14:00 +00:00
Patrick Honkonen
9c2a902b51 [PM-16120] Defer passkey authentication until vault data is loaded (#4524) 2025-01-07 21:00:49 +00:00
renovate[bot]
69da467b7c [deps]: Lock file maintenance (#4502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 20:58:20 +00:00
renovate[bot]
c8d3f341a8 [deps]: Update gh minor (#4496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-01-07 20:26:31 +00:00
David Perez
3717e33d00 PM-16821: remove padding on right side of the vault screen dividers (#4528) 2025-01-07 16:54:22 +00:00
Dave Severns
7ab72543a3 PM-14333 fix case of crowdin translation not adding annotations on string with format args (#4505) 2025-01-07 15:30:34 +00:00
Dave Severns
80f31cdff9 PM-16474 Adding custom field issues when another text field holds focus (#4511) 2025-01-07 14:52:12 +00:00
bw-ghapp[bot]
40056f3181 Autosync Crowdin Translations (#306) 2025-01-07 13:58:44 +00:00
renovate[bot]
b0e9703d9f [deps]: Update kotlin (#4501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 22:03:10 +00:00
Phil Cappelli
0ca97de1b2 BWA-118 Tutorial text cut off in landscape mode (#312) 2025-01-06 14:55:21 -05:00
mpbw2
b958734946 [PM-13349] Hide edit button unless item is in at least one non-readOnly collection (#4430) 2025-01-06 17:59:30 +00:00
renovate[bot]
2acede98f3 [deps]: Lock file maintenance (#310) 2025-01-06 17:57:59 +00:00
renovate[bot]
fb2edf43be [deps]: Update gh minor (#307) 2025-01-06 17:56:49 +00:00
bw-ghapp[bot]
484faedcc5 Autosync Crowdin Translations (#4503)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2025-01-06 17:31:37 +00:00
André Bispo
8bde8741dd [PM-8217] Add local feature flag to ignore environment validation (#4521) 2025-01-06 15:32:53 +00:00
André Bispo
d5a02e6285 [PM-15969] Users with Can Edit access cannot assign collections (#4522) 2025-01-06 15:11:16 +00:00
André Bispo
a35ec8cf3c [PM-8217] New device two factor notice (#4508)
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2024-12-27 15:03:33 +00:00
Dave Severns
ae8db9256c Update the text field to not use passed in modifier. (#4506) 2024-12-23 20:51:11 +00:00
André Bispo
688dd3a39b [PM-8217] Add creationDate and isTwoFactorEnable properties (#4504) 2024-12-23 18:56:55 +00:00
Dave Severns
6223f362c3 PM-16062 Prevent account locks for ongoing autofill requests (#4498) 2024-12-20 22:05:30 +00:00
Dave Severns
1148e4821c PM-14333 Complete fix for crash caused by spannable text creation (#4479) 2024-12-20 21:45:55 +00:00
Patrick Honkonen
f32eecc0d7 [PM-15864] Add copy private key action for SSH keys (#4462) 2024-12-20 19:44:31 +00:00
renovate[bot]
2ba516f50f [deps]: Lock file maintenance (#4497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 19:20:14 +00:00
André Bispo
c4c7af54ff [PM-8217] New device notice two factor UI (#4401) 2024-12-20 18:08:23 +00:00
Patrick Honkonen
ae0bf6318d [PM-15176] Update script path for CI build info (#4493) 2024-12-20 18:06:56 +00:00
Patrick Honkonen
3be2242431 [PM-15643] Show FAB in empty item type filters (#4490) 2024-12-20 17:52:04 +00:00
Patrick Honkonen
d9c0911238 [PM-12391] Respect PIN unlock setting during FIDO user verification (#4483) 2024-12-20 17:51:29 +00:00
Patrick Honkonen
5aa8369ac5 [PM-15863] Request master password before revealing private SSH key (#4481) 2024-12-20 17:48:01 +00:00
Patrick Honkonen
35e8cecdcf [PM-15970] Allow assigning collections if user has correct permissions (#4461) 2024-12-20 17:33:09 +00:00
André Bispo
a7939414ae [PM-8217] New device notice email access UI (#4400) 2024-12-20 16:53:30 +00:00
Dave Severns
6c355ae5b7 PM-15383 PM-15381 - Show the google play review prompt (#4455)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2024-12-20 15:30:39 +00:00
Álison Fernandes
843247b02d [PM-16211] chore(ci): Fix hotfix branch creation workflow by retrieving the last tag across all branches (#4491) 2024-12-20 14:30:46 +00:00
Patrick Honkonen
efbb8446e3 [PM-15057] Update AndroidX Credentials to 1.5.0-alpha04 (#4447) 2024-12-20 14:27:41 +00:00
bw-ghapp[bot]
a279a2b1a3 Autosync Crowdin Translations (#4494)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-12-20 14:19:32 +00:00
Lucas
3329dfaf20 [PM-15912] Fix alphabetical order in FIDO2 privileged browser community list (#4451) 2024-12-19 18:22:07 +00:00
Álison Fernandes
b615bfa664 [PM-16208] chore(ci): Split scan workflow for protected branches and migrate to new sonarqube action (#4489) 2024-12-18 23:49:41 +00:00
Álison Fernandes
f6bd467ec8 [PM-16207] chore(ci): Fix codecov usage and remove secrets from test.yml (#4488) 2024-12-18 21:48:12 +00:00
Dave Severns
8548c74d58 PM-16058 add test tag parameter to be applied to the text field (#4487) 2024-12-18 18:01:40 +00:00
Dave Severns
e2b93ec08c Add Phil to CODEOWNERS (#4480) 2024-12-17 19:07:27 +00:00
Dave Severns
e565a5a118 PM-15890 TLS related error propagation (#4454) 2024-12-17 17:56:29 +00:00
aj-rosado
3a41138f39 [PM-10515] CI build info on version copy (#4456)
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
2024-12-17 17:36:46 +00:00
Dave Severns
889457ae96 PM-15037 Correct the text for the confirm error dialog on import logins screen (#4478) 2024-12-16 18:27:06 +00:00
Patrick Honkonen
be88cdf42e [PM-15176] Rename bundle and apk files to match applicationId and flavor (#4474) 2024-12-14 14:36:01 +00:00
David Perez
e37cefeb1d PM-16058 - Add default environments via autocomplete dropdown (#4473) 2024-12-13 23:04:02 +00:00
Phil Cappelli
ae20e55b1a PM-16053 - Text in Prompt to Restart App After Changing Language in Settings (#4472) 2024-12-13 22:26:00 +00:00
Patrick Honkonen
bd29e1738c [PM-16052] Add CI_INFO build config field (#4471) 2024-12-13 20:49:09 +00:00
David Perez
f28f5ee688 Update camera libraries (#4468) 2024-12-13 20:39:13 +00:00
David Perez
c92c334e03 Update the Firebase libraries (#4469) 2024-12-13 19:36:05 +00:00
David Perez
fb8f260a94 Update the Compose BOM (#4470) 2024-12-13 19:17:03 +00:00
Patrick Honkonen
9635bd9b43 [BWA-33]: Publish release bundles to Play Store when requested (#303) 2024-12-13 13:36:54 -05:00
Álison Fernandes
9f5e97b8c9 [PM-15176] chore(ci): Fix fastlane build artifacts names and filepaths (#4458) 2024-12-13 18:25:42 +00:00
bw-ghapp[bot]
8dd9ba65d0 Autosync Crowdin Translations (#302) 2024-12-13 12:48:37 -05:00
Patrick Honkonen
1aec94ee7d [PM-15176] Rename AAB outputs to match APK naming convention (#4467) 2024-12-13 16:43:49 +00:00
David Perez
d4b153107a Update to Hilt 2.53.1 (#4466) 2024-12-13 16:34:59 +00:00
Phil Cappelli
d405a0d04b PM-15976 - App crashes when non-english language user tries to create account (#4460) 2024-12-13 16:24:35 +00:00
David Perez
86587258c9 Remove unused google-services.json.enc (#4465) 2024-12-13 15:57:42 +00:00
bw-ghapp[bot]
7571d35fb3 Autosync Crowdin Translations (#4463)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-12-13 14:50:33 +00:00
André Bispo
c7bef56639 [PM-15553] Add remote flag to control cipher key encryption. (#4457) 2024-12-12 22:08:18 +00:00
Lucas
d5a75c2d53 [PM-15911] Add Firefox Nightly to FIDO2 community list (#4450) 2024-12-12 16:53:05 +00:00
Phil Cappelli
ad90785c39 BWA-124 - 'Copy' Option Missing from Long-Press Menu (#300) 2024-12-12 00:28:59 -05:00
Patrick Honkonen
e11b0c7839 [PM-15862] Remove Linked Fields option from SSH keys (#4453) 2024-12-12 00:11:12 +00:00
David Perez
a6929f2d8d Ensure DebugTree is only planted once (#4452) 2024-12-11 21:33:01 +00:00
Dave Severns
9b064eea90 PM-15380 Track user interactions which would trigger a potential showing of the app review prompt. (#4415) 2024-12-11 18:32:15 +00:00
Patrick Honkonen
d9ef87e21f [PM-15609] Move FIDO2 origin validation logic to Fido2OriginManager (#4426) 2024-12-11 18:17:51 +00:00
Patrick Honkonen
7b3ad98698 [PM-15176] Update build output filenames (#4446) 2024-12-11 17:20:40 +00:00
Patrick Honkonen
ea1a4e4710 Update CODEOWNERS (#301) 2024-12-11 10:31:45 -05:00
Phil Cappelli
2f678ba32e BWA-118 - Tutorial text cut off in landscape mode (#299) 2024-12-11 10:13:51 -05:00
David Perez
c00cdc7407 Run formatter on the app (#4444) 2024-12-09 22:21:49 +00:00
renovate[bot]
4bab5a59fc [deps]: Update sonarsource/sonarcloud-github-action action to v4 (#4434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 21:47:32 +00:00
Dave Severns
4932353003 PM-15037 Add missing title to empty sync import logins error dialog (#4443) 2024-12-09 19:31:38 +00:00
David Perez
5997579330 PM-15599: Allow for custom TextToolbars (#4440) 2024-12-09 19:22:44 +00:00
renovate[bot]
7abb52b42d [deps]: Lock file maintenance (#4435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 18:36:49 +00:00
renovate[bot]
5b0b3b6c70 [deps]: Update gradle minor (#295) 2024-12-09 13:35:35 -05:00
renovate[bot]
65d361625e [deps]: Lock file maintenance (#298) 2024-12-09 18:33:21 +00:00
renovate[bot]
8d09d9d604 [deps]: Update gh minor (#296) 2024-12-09 13:32:25 -05:00
Andrew Haisting
41cf116e6d BITAU-217 Update "Move to Bitwarden" copy to "Copy to Bitwarden" (#283) 2024-12-09 18:31:30 +00:00
Phil Cappelli
ddfd9bd0d8 PM-15831 - Enable remote configuration of enable-authenticator-sync-android feature flag (#4441) 2024-12-09 18:19:25 +00:00
renovate[bot]
5abdf1e4b0 [deps]: Update gh minor (#4433)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 14:55:41 +00:00
aj-rosado
4234008337 [PM-11139] Setting icon on passkeys (#4409) 2024-12-09 12:51:40 +00:00
Dave Severns
7b7c2da67c PM-15624 Align handling of no network states with iOS app. (#4431) 2024-12-06 22:24:38 +00:00
Patrick Honkonen
12dd865cec [PM-15116] Add common fields to SSH Key add/edit screen (#4428) 2024-12-06 21:49:16 +00:00
github-actions[bot]
ff09bdd110 Autosync Crowdin Translations (#293) 2024-12-06 12:04:08 -05:00
Andy Pixley
ec788f2472 [BRE-471] Update to use app generated token (#294) 2024-12-06 11:57:33 -05:00
bw-ghapp[bot]
0c53fa6c0b Autosync Crowdin Translations (#4427)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-12-06 14:28:32 +00:00
aj-rosado
b2eae2c33f [PM-13513] Keeping "androidapp" scheme on uri when saving from Android Apps (#4420) 2024-12-06 10:14:53 +00:00
Patrick Honkonen
7da1e48aa5 [PM-15057] Rename toFido2RequestOrNull to toFido2CreateRequestOrNull (#4425) 2024-12-05 18:20:29 +00:00
Álison Fernandes
10b6f533b2 [PM-9328] Mobile team owns changes to the .github folder (#4423) 2024-12-05 17:54:23 +00:00
David Perez
6a77dbe8b5 PM-15599: Update copy toast to not display copied value (#4424) 2024-12-05 17:00:53 +00:00
Álison Fernandes
6f7aedbcd2 [PM-15583] chore: Adds Autofill failure report form to GitHub issues menu (#4422) 2024-12-05 16:45:02 +00:00
Dave Severns
97285f463e PM-15514 add feature flag key for app review prompt (#4414) 2024-12-03 20:49:37 +00:00
renovate[bot]
6e643cb43b [deps]: Update kotlin (#285) 2024-12-03 12:41:34 -05:00
renovate[bot]
6cb734fb94 [deps]: Update gradle minor (#286) 2024-12-03 11:52:32 -05:00
Phil Cappelli
df846374f5 PM-15147 - MasterPasswordGuidanceScreen PR Cleanup (#4411) 2024-12-03 16:35:15 +00:00
Patrick Honkonen
65ff843ada [PM-15057] Add utility for loading FIDO2 icons (#4371) 2024-12-03 16:04:08 +00:00
renovate[bot]
26a7876525 [deps]: Update org.sonarqube to v6 (#4381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 16:03:12 +00:00
renovate[bot]
718cece22e [deps]: Update kotlin (#4378)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: David Perez <david@livefront.com>
2024-12-03 15:37:26 +00:00
github-actions[bot]
00ce3c038b Autosync Crowdin Translations (#282) 2024-12-03 15:30:29 +00:00
renovate[bot]
d4e7211156 [deps]: Update org.sonarqube to v6 (#288) 2024-12-03 15:29:53 +00:00
renovate[bot]
1b3e254a3f [deps]: Lock file maintenance (#289) 2024-12-03 10:28:15 -05:00
renovate[bot]
52748ba66f [deps]: Update codecov/codecov-action action to v5 (#287) 2024-12-03 10:26:28 -05:00
André Bispo
86057b5923 [BWA-110] Retrofit ProGuard missing rule (#291) 2024-12-03 15:24:47 +00:00
Dave Severns
ef8223bd8b PM-15431 allow background activities to start by NFC manager for the … (#4410) 2024-12-03 15:24:36 +00:00
renovate[bot]
7d1e2fec90 [deps]: Update gh minor (#284) 2024-12-03 15:23:04 +00:00
David Perez
382597f356 Update Dagger Hilt library (#4406) 2024-12-02 23:29:19 +00:00
Phil Cappelli
45200d0480 PM-15067 - Replace "account" with "vault" in subtitle (#4402) 2024-12-02 22:25:53 +00:00
David Perez
1534fb598b Update to latest AGP (#4404) 2024-12-02 20:33:03 +00:00
Dave Severns
819cc625a1 PM-14995 Hide TOTP for non premium org items even if individual user has premium account (#4390) 2024-12-02 20:06:11 +00:00
Patrick Honkonen
7e82b6e400 [PM-15116] Add common vault item content to SSH keys (#4365) 2024-12-02 19:51:59 +00:00
renovate[bot]
02c44f514a [deps]: Update codecov/codecov-action action to v5 (#4380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 19:51:08 +00:00
renovate[bot]
6ef5d4b6aa [deps]: Lock file maintenance (#4382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 19:47:15 +00:00
David Perez
cb8c1341e2 Update to Robolectric 4.14.1 (#4403) 2024-12-02 19:46:23 +00:00
Phil Cappelli
b2391dd66a PM-15147 - Design Audit - Master Password Guidance Screen (#4383) 2024-12-02 19:38:45 +00:00
bw-ghapp[bot]
bca9f5e859 Autosync Crowdin Translations (#4396)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-12-02 16:49:41 +00:00
renovate[bot]
b654ef1b43 [deps]: Update gh minor (#4379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 16:48:58 +00:00
Opeyemi
348abcefe9 [BRE-443] - Fix bwwl Linting pre Deployment (#4384) 2024-11-28 11:28:19 +00:00
Dave Severns
a96fcd944e PM-15412 Pull-to-refresh operations should not invoke a forced sync. (#4388) 2024-11-26 21:00:26 +00:00
David Perez
05aa52b032 Remove unused lastDatabaseSchemeChangeInstant from settings disk source (#4374) 2024-11-25 22:59:17 +00:00
David Perez
cce9befe8c Ensure lastSyncTime is updated before resyncing the vault (#4375) 2024-11-25 15:03:58 +00:00
David Perez
8e7ec7af4c PM-15177: Improve destructive fallback logic (#4373) 2024-11-22 22:21:15 +00:00
Dave Severns
2e1845887c PM-15022 Auto login when user completes a YubiKey login trigger. (#4368) 2024-11-22 18:01:49 +00:00
David Perez
c1be5be188 M-15177: All user input syncs should be forced (#4369) 2024-11-22 17:40:40 +00:00
Patrick Honkonen
76b6853f90 [PM-15113] Disable add button in SSH Keys screen (#4364) 2024-11-22 17:31:47 +00:00
Patrick Honkonen
89935ac42b [PM-15054] Add API for importing ciphers (#4339) 2024-11-22 17:30:59 +00:00
Phil Cappelli
050b3b3007 PM-15067 - Design Audit - Prevent Account Lockout Screen (#4361) 2024-11-22 17:14:18 +00:00
Patrick Honkonen
249dbdaaf8 [PM-15057] Rename Fido2CredentialRequest to Fido2CreateCredentialRequest (#4362) 2024-11-22 15:49:00 +00:00
Patrick Honkonen
dbb006d745 [PM-15064] Add feature flags for CXP import and export (#4337) 2024-11-22 15:22:14 +00:00
Patrick Honkonen
5d4197076c [PM-15050] Track vault registration for CXP export in settings (#4335) 2024-11-22 15:10:00 +00:00
Dave Severns
1e223b1a2a PM-15109 only accept numeric values for account pin lock value (#4359) 2024-11-22 13:59:01 +00:00
bw-ghapp[bot]
b19e7e1495 Autosync Crowdin Translations (#4363)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-11-22 01:54:52 +00:00
David Perez
1ef7e2173b Remove the BasicDialogState (#4360) 2024-11-21 21:19:12 +00:00
David Perez
bef6ffc094 Remove unused constant (#4358) 2024-11-21 20:50:37 +00:00
Dave Severns
06b08d595b Refactor usage of the ContentCard to always use the ContentBlock component. (#4357) 2024-11-21 20:27:18 +00:00
David Perez
4fb031d76c Simplify the usage of the BitwardenLoadingDialog (#4356) 2024-11-21 20:16:02 +00:00
David Perez
c4f13fc8bd Share the environment flow and provide better default (#4355) 2024-11-21 20:15:44 +00:00
David Perez
8a534d11d2 Simplify url check in BaseUrlInterceptor (#4354) 2024-11-21 20:15:24 +00:00
David Perez
57ea58fc3c Log vault deserialization errors (#4353) 2024-11-21 19:35:57 +00:00
David Perez
245fcd7502 PM-14963: Add toast when login via device succeeds (#4351) 2024-11-21 15:42:24 +00:00
Dave Severns
dbeb00ba1c PM-15036 Show visual feedback for the send code on export vault. (#4346) 2024-11-21 15:25:44 +00:00
David Perez
cbfd7ad1b1 Simplify the usage of basic dialogs (#4347) 2024-11-21 15:23:40 +00:00
Álison Fernandes
d4033a7705 [PM-11598] GitHub Release - Improve tag name and refactor inputs casing (#4349) 2024-11-21 12:52:59 +00:00
David Perez
96bd25eae5 PM-12733: Add error dialog to be displayed if TOTP code is blank (#4345) 2024-11-20 22:00:08 +00:00
Dave Severns
ec8e934bf4 PM-15062 Checking if the user has a no longer supported biometric as their only way of unlocking their account. (#4338) 2024-11-20 20:45:26 +00:00
David Perez
3092ba1fc6 PM-15110: Ensure all network requests always use the current environment data (#4344) 2024-11-20 19:36:43 +00:00
David Perez
5ea17700b3 PM-15025: Update sendVerificationEmail to handle error responses (#4336) 2024-11-19 20:03:07 +00:00
aj-rosado
d418444dc0 [PM-13831] Add copy button identity and note fields (#4302) 2024-11-19 16:31:00 +00:00
Dave Severns
531b003347 PM-15049 PW strength indicator design audit (#4334) 2024-11-19 15:16:27 +00:00
Dave Severns
dca88a58e7 PM-15037 Update Import Logins for design audit (#4333) 2024-11-19 15:16:16 +00:00
David Perez
da878a9fab PM-15041: Update stepper buttons (#4330) 2024-11-19 15:04:41 +00:00
David Perez
95552a7a55 PM-15040: Update Login screen button icons (#4329) 2024-11-19 15:04:16 +00:00
David Perez
90b638eff0 PM-15039: Update welcome screen for design audit (#4328) 2024-11-19 15:03:51 +00:00
David Perez
ccd4fd9aba PM-15038: Update custom switches to use standard component (#4327) 2024-11-19 15:03:23 +00:00
github-actions[bot]
5e94b1b689 Autosync Crowdin Translations (#277) 2024-11-18 22:43:19 +00:00
André Bispo
f3f51cf244 [BWA-86] Debug Menu #4 (#276) 2024-11-18 22:39:03 +00:00
André Bispo
5f109b5085 [BWA-86] Debug Menu #3 - feature flag service (#275) 2024-11-18 22:24:06 +00:00
David Perez
2d15c4864f Log JWT parsing errors (#4326) 2024-11-18 22:13:55 +00:00
ifernandezdiaz
b183f7af42 QA-999: Adding testTags for account switching options (#4324) 2024-11-18 22:03:19 +00:00
Dave Severns
2ece0856d4 PM-12761 Talkback UI Focus misalignment bug. (#4325) 2024-11-18 21:41:50 +00:00
David Perez
429c76ce03 PM-14200: Add count to sends type header (#4323) 2024-11-18 21:35:38 +00:00
André Bispo
a0cc8a8a3d [BWA-86] Debug Menu #2 - config service (#274) 2024-11-18 21:31:25 +00:00
André Bispo
5b53b50b01 [BWA-86] Debug Menu #1 - network layer (#272) 2024-11-18 21:11:09 +00:00
bw-ghapp[bot]
506d0f13c7 Autosync Crowdin Translations (#4322)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-11-18 18:22:19 +00:00
Vince Grassia
6a0a7d70bc BRE-438 - Update Crowdin workflow to use app token (#4321) 2024-11-18 17:52:04 +00:00
David Perez
3742940290 Update the Firebase BOM (#4317) 2024-11-18 15:06:08 +00:00
David Perez
1cb647b7ae Update compose BOM to 2024.11.0 (#4316) 2024-11-18 15:05:51 +00:00
David Perez
ffeae93728 PM-12733: Trim totp codes before saving them (#4315) 2024-11-18 15:05:00 +00:00
Patrick Honkonen
e90bd136f6 [PM-10483] Fix collection manage check for delete permission (#4313) 2024-11-18 14:26:58 +00:00
David Perez
30eb11b85e PM-14409: Add realtime check for when the accessibility service is enabled or disabled (#4314) 2024-11-15 22:15:19 +00:00
github-actions[bot]
a04598c77a Autosync Crowdin Translations (#4307)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-11-15 20:32:21 +00:00
David Perez
cad2df79b6 PM-14934: Allow accessibility autofill to fill just a username or just a password (#4312) 2024-11-15 20:15:02 +00:00
David Perez
089136552b PM-12259: Use validatePin SDK to validate the users pin (#4311) 2024-11-15 19:56:55 +00:00
Dave Severns
1b0bc13903 Fix typos in generator actions (#4310) 2024-11-15 16:32:09 +00:00
Patrick Honkonen
d125fab0b7 [PM-14843] Allow deletion of items in collections with manage permission (#4299) 2024-11-15 16:10:32 +00:00
renovate[bot]
abbf6565d4 [deps]: Update gradle minor (#280) 2024-11-11 19:48:34 +00:00
renovate[bot]
5263329ab5 [deps]: Lock file maintenance (#281) 2024-11-11 14:28:49 -05:00
renovate[bot]
c3d2c17830 [deps]: Update kotlin (#279) 2024-11-11 14:27:56 -05:00
renovate[bot]
29213421ec [deps]: Update gh minor (#278) 2024-11-11 14:27:07 -05:00
Andrew Haisting
5184be0e98 BITAU-185 Show "Local codes" header on item list screen (#269) 2024-11-08 15:30:10 -06:00
Andrew Haisting
0643ecc00b BITAU-195 Allow issuer to be null for shared items (#268) 2024-11-08 15:03:19 -06:00
Andrew Haisting
7e5dcd3814 BITAU-197 Update copy in all Save to Bitwarden scenarios (#271) 2024-11-08 14:47:37 -06:00
Andrew Haisting
6763bfd997 BITAU-198 Update some copy on the manual entry screen (#273) 2024-11-05 08:54:59 -06:00
github-actions[bot]
aeba03a769 Autosync Crowdin Translations (#267)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-11-05 13:52:59 +00:00
Andrew Haisting
7f25fa07a4 Use authenticatorbridge version 1.0.0 (#270) 2024-11-01 16:41:40 -05:00
Andrew Haisting
8b00773c84 BITAU-193 Make first time sync snackbar correct in dark theme (#266) 2024-10-31 16:54:58 -05:00
Andrew Haisting
b42ec0ae13 BITAU-89 Have SettingsViewModel obsererve DefaultSaveOption (#265)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2024-10-31 21:33:02 +00:00
Andrew Haisting
78d7865207 BITAU-178 Show shared codes on the search screen (#264)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2024-10-31 21:32:52 +00:00
Andrew Haisting
5ac5e31dd2 BITAU-184 Allow user to save to Bitwarden when adding a code manually (#263) 2024-10-30 15:12:54 -05:00
Andrew Haisting
bcc7e6756e BITAU-84 Show Snackbar the first time a user syncs accounts (#259) 2024-10-30 11:12:47 -05:00
Andrew Haisting
aa42e0ad18 BITAU-189 BITAU-188 Only show default save option row when sync is en… (#261) 2024-10-30 10:32:57 -05:00
Andrew Haisting
c0abc1d647 BITAU-76 Update copy on download Bitwarden action card (#260) 2024-10-30 10:06:34 -05:00
Andrew Haisting
5de6dc3473 BITAU-89 Show save location dialog on the QR scan screen (#258) 2024-10-30 10:02:03 -05:00
Patrick Honkonen
bb6255b6a4 Disable ExtraTranslation lint check (#262) 2024-10-29 18:12:21 -04:00
renovate[bot]
408d01d546 [deps]: Update com.google.devtools.ksp to v2.0.21-1.0.26 (#254) 2024-10-28 12:43:00 -04:00
renovate[bot]
3b9e42a256 [deps]: Update gradle minor (#256) 2024-10-28 14:46:02 +00:00
github-actions[bot]
6d500e52e7 Autosync Crowdin Translations (#253)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-10-28 14:43:02 +00:00
renovate[bot]
3f72b49286 [deps]: Lock file maintenance (#257) 2024-10-28 14:42:34 +00:00
renovate[bot]
8b9f13e9e7 [deps]: Update gh minor (#255) 2024-10-28 10:38:52 -04:00
Andrew Haisting
1d2095b0b3 BITAU-181 Allow user to update default save options from settings (#252) 2024-10-24 09:49:07 -05:00
Andrew Haisting
5fdfb26950 BITAU-180 Show "Move to Bitwarden" long press action (#250) 2024-10-24 08:38:55 -05:00
Andrew Haisting
5d7656323b Add queries to AndroidManifest (#251) 2024-10-23 09:53:16 -05:00
vphan916
932c26dfc6 QA Automation - Locators for Android (#214) 2024-10-23 13:48:22 +00:00
renovate[bot]
5c38522ec6 [deps]: Update kotlin (#235) 2024-10-23 09:39:59 -04:00
Andrew Haisting
ab4d9b9984 BITAU-78 Show SyncWithBitwarden action card (#248) 2024-10-21 17:21:26 -05:00
github-actions[bot]
74b1bc8303 Autosync Crowdin Translations (#247) 2024-10-21 22:07:01 +00:00
Patrick Honkonen
25e01c86bc Mark IntentManagerImpl as not covered by code coverage (#249) 2024-10-21 16:25:14 -05:00
Andrew Haisting
b127021701 Distribute Firebase builds to livefront group. (#243) 2024-10-17 15:25:00 -05:00
Andrew Haisting
123d227c13 Add closing paren (#245) 2024-10-17 15:24:46 -05:00
Andrew Haisting
88b96812de BITAU-82 Show shared codes on the item listing screen (#241) 2024-10-17 15:24:21 -05:00
Andrew Haisting
8abe62e53e Remove Firebase APK distribution (#244) 2024-10-17 11:51:57 -05:00
Andrew Haisting
d07a9dcf81 BITAU-90 Add "Sync with Bitwarden App" row to settings (#239) 2024-10-15 15:38:53 -05:00
Andrew Haisting
1820073b65 BITAU-68 Specify correct authenticatorbridge connection type (#240)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2024-10-15 15:32:50 -05:00
Andrew Haisting
1669fa4044 Only run debug test variant on CI (#242) 2024-10-15 15:31:31 -05:00
renovate[bot]
d3c1f8e26a [deps]: Update gh minor (#234) 2024-10-15 15:17:03 -04:00
renovate[bot]
a78f81014e [deps]: Lock file maintenance (#236) 2024-10-15 15:13:52 -04:00
github-actions[bot]
4c74f720eb Autosync Crowdin Translations (#233) 2024-10-14 17:28:29 +00:00
Andrew Haisting
03251abe88 Update authenticatorbridge .aar after removing lastSyncTime (#237) 2024-10-14 11:56:46 -05:00
Andrew Haisting
43fb630393 BITAU-76 Show download Bitwarden action card (#229) 2024-10-10 15:22:54 -05:00
Andrew Haisting
54e59cc61b Return noop bridge manager when password sync feature flag is off (#232)
Co-authored-by: Brian Yencho <brian@livefront.com>
2024-10-10 10:40:46 -05:00
Andrew Haisting
d6ae7da44c Fix empty state for totp codes (#231) 2024-10-09 09:08:41 -05:00
renovate[bot]
280aa35b73 [deps]: Update gradle minor (#222) 2024-10-09 10:01:06 -04:00
Andrew Haisting
f3bce23942 Use correct feature for syncing with Bitwarden (#230) 2024-10-08 15:08:35 -05:00
Andrew Haisting
1015654d27 BITAU-70 Implement symmetric key storage (#226) 2024-10-08 13:01:55 -05:00
Patrick Honkonen
d8c80f7e28 [BWA-10] Handle exception when checking for suspicious intents (#228) 2024-10-08 08:20:44 -05:00
Patrick Honkonen
3aaa93675d Update Android Gradle plugin and Gradle wrapper (#225) 2024-10-08 08:18:29 -05:00
Andrew Haisting
af0f894dd3 BITAU-83 Refactor AuthenticatorRepository to accomodate shared codes (#220)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2024-10-07 19:47:05 -05:00
renovate[bot]
6e7f5a31f1 [deps]: Update androidx.compose:compose-bom to v2024.09.03 (#221) 2024-10-07 09:37:47 -04:00
github-actions[bot]
79b7866e30 Autosync Crowdin Translations (#219) 2024-10-07 13:35:38 +00:00
renovate[bot]
d8da7a8cc2 [deps]: Update gh minor (#223) 2024-10-07 09:32:09 -04:00
renovate[bot]
e36e9e878f [deps]: Lock file maintenance (#224) 2024-10-07 13:27:22 +00:00
Andrew Haisting
93423d4b13 BITAU-68 Specify build specific bridge permission in manifest (#218) 2024-10-03 13:45:27 -05:00
Patrick Honkonen
ee165491c9 [BWA-90] Remove WorkManager for clipboard clearing (#216) 2024-10-02 14:57:22 -04:00
Patrick Honkonen
8ad9bc5eed [BWA-91] Add OmitFromCoverage annotation (#217) 2024-10-02 14:43:07 -04:00
renovate[bot]
e2e49a4555 [deps]: Update kotlin (#193) 2024-10-02 10:22:58 -04:00
Patrick Honkonen
a92fd5b35d [deps]: Update androidxLifecycle to 2.8.6 (#213) 2024-10-02 15:49:39 +02:00
Patrick Honkonen
5e5b29677a [deps]: Update compose to 2024.09.02 (#212) 2024-10-02 15:49:22 +02:00
Andrew Haisting
9f4a85d373 remove unused getAuthCodeFlow() (#215) 2024-10-01 15:33:21 -05:00
vphan916
b0edb7cf3b Added locators for the settings page and the Add Code button (#203) 2024-09-30 13:45:27 -07:00
Andrew Haisting
0d5bbc177c BITAU-67 Add custom authenticator bridge permission to manifest (#211) 2024-09-30 14:40:02 -05:00
github-actions[bot]
f7c14890f1 Autosync Crowdin Translations (#202) 2024-09-30 19:39:15 +00:00
renovate[bot]
924ff87979 [deps]: Update gradle minor (#206) 2024-09-30 19:36:01 +00:00
renovate[bot]
713bb51974 [deps]: Update org.testng:testng to v7 (#208) 2024-09-30 15:35:28 -04:00
renovate[bot]
6faf720076 [deps]: Update gh minor (#207) 2024-09-30 15:34:51 -04:00
renovate[bot]
a13a7c363f [deps]: Update ubuntu to v24 (#209) 2024-09-30 17:58:29 +00:00
renovate[bot]
9d6192ba1f [deps]: Lock file maintenance (#210) 2024-09-30 17:57:45 +00:00
Patrick Honkonen
ddeece0a5c [BWA-79] Sanitize manual entry code before validation (#200) 2024-09-26 10:22:10 -04:00
Andrew Haisting
3445191a05 BITAU-66 Use fixed keystore for signing debug build (#201) 2024-09-25 14:17:36 -05:00
github-actions[bot]
2d75679586 Autosync Crowdin Translations (#197) 2024-09-25 12:48:38 +00:00
Álison Fernandes
d676a6c76c [BWA-77] Setup codeowners (#198) 2024-09-25 08:45:06 -04:00
renovate[bot]
26eeb39f88 [deps]: Update gradle minor (#192) 2024-09-19 16:35:00 -04:00
Patrick Honkonen
49c2e3030c [PM-12166] Update detekt (#196) 2024-09-19 18:06:34 +01:00
renovate[bot]
ea267a0298 [deps]: Lock file maintenance (#195) 2024-09-19 12:47:26 -04:00
renovate[bot]
6e0063c977 [deps]: Update gh minor (#194) 2024-09-19 12:47:00 -04:00
Patrick Honkonen
1f89314bbb [BWA-65] Sort items alphabetically with special char precedence (#190) 2024-09-18 13:10:16 -03:00
github-actions[bot]
afd7097b53 Autosync Crowdin Translations (#186) 2024-09-18 15:00:57 +00:00
Andrew Haisting
087cd5a093 BITAU-158 Add PasswordManagerSync local feature flag (#189) 2024-09-18 09:12:13 -05:00
renovate[bot]
06d742bd16 [deps]: Update gradle minor (#183) 2024-09-03 14:13:38 -04:00
renovate[bot]
05bcaf9dbc [deps]: Lock file maintenance (#184) 2024-09-03 13:13:05 +00:00
renovate[bot]
d37fa652bb [deps]: Update org.jetbrains.kotlinx:kotlinx-serialization-json to v1.7.2 (#181) 2024-09-03 13:12:48 +00:00
renovate[bot]
170e74db55 [deps]: Update gh minor (#182) 2024-09-03 13:11:12 +00:00
github-actions[bot]
b75133516d Autosync Crowdin Translations (#180) 2024-09-03 13:09:49 +00:00
Patrick Honkonen
e90c41a3b7 [BWA-61] Fix pre-existing detekt issues (#179) 2024-09-03 09:05:20 -04:00
Patrick Honkonen
5758b34dcf [BWA-60] Configure detekt scanning (#178) 2024-08-29 13:50:17 -04:00
Patrick Honkonen
b49d8a18a2 [BWA-59] Define feature flag manager (#177) 2024-08-29 13:24:27 -04:00
Patrick Honkonen
0d77e7085b [BWA-58] Define feature flag repo (#176) 2024-08-29 14:26:24 +00:00
Patrick Honkonen
d0203eedc4 [BWA-57] Define feature flag disk source (#175) 2024-08-29 10:22:14 -04:00
Patrick Honkonen
3c74a342d1 [BWA-56] Add network module (#174) 2024-08-29 09:43:51 -04:00
renovate[bot]
8503610b0b [deps]: Update com.google.firebase:firebase-bom to v33.2.0 (#172) 2024-08-27 15:37:43 -04:00
renovate[bot]
ae1225b948 [deps]: Update kotlin (#171) 2024-08-27 14:54:24 -04:00
github-actions[bot]
959361adc2 Autosync Crowdin Translations (#169) 2024-08-23 17:43:56 +00:00
renovate[bot]
f9ccb766c2 [deps]: Lock file maintenance (#173) 2024-08-23 17:42:12 +00:00
renovate[bot]
e5e5d3c67c [deps]: Update github/codeql-action action to v3.26.4 (#170) 2024-08-23 17:41:25 +00:00
renovate[bot]
ea1813a1b6 [deps]: Lock file maintenance (#166) 2024-08-21 09:30:21 -04:00
renovate[bot]
b1f0a10a55 [deps]: Update sonarsource/sonarcloud-github-action action to v3 (#165) 2024-08-21 09:29:52 -04:00
renovate[bot]
1af16bfbef [deps]: Update github/codeql-action action to v3.26.3 (#164) 2024-08-21 09:29:19 -04:00
Martini
524e0941e4 [BWA-53] Fix: Resolve issue with code copying in search results (#163) 2024-08-20 09:17:32 -04:00
github-actions[bot]
bcea5a6b25 Autosync Crowdin Translations (#158) 2024-08-19 13:28:34 +00:00
renovate[bot]
b2a256e2fc [deps]: Update gradle minor (#160) 2024-08-19 09:23:40 -04:00
renovate[bot]
83c8db6867 [deps]: Update github/codeql-action action to v3.26.2 (#159) 2024-08-19 09:23:11 -04:00
renovate[bot]
96e2985aed [deps]: Update gradle/actions action to v4 (#161) 2024-08-19 09:22:37 -04:00
renovate[bot]
5e544ff1f9 [deps]: Lock file maintenance (#162) 2024-08-19 09:22:02 -04:00
github-actions[bot]
58c31937a2 Autosync Crowdin Translations (#153) 2024-08-13 16:22:53 +00:00
Patrick Honkonen
01ac1f3f93 [BWA-52] Disable MissingTranslation lint error (#156) 2024-08-13 12:19:10 -04:00
renovate[bot]
f93d11fe36 [deps]: Update kotlin (#151) 2024-08-13 12:12:50 -04:00
renovate[bot]
44a53f18d3 [deps]: Update gradle minor (#150) 2024-08-13 11:04:53 -04:00
renovate[bot]
72218b643d [deps]: Update gh minor (#148) 2024-08-13 10:48:24 -04:00
Patrick Honkonen
d00a77e247 [BWA-47] Update gradle wrapper validation GH action (#145) 2024-08-13 10:18:44 -03:00
Patrick Honkonen
6e9f9d62a1 [BWA-28] Read Crowdin API token from secrets (#152) 2024-08-13 09:18:07 -04:00
renovate[bot]
48f30f022d [deps]: Update gradle minor (#149) 2024-07-23 13:08:54 +00:00
Matt Bishop
2f526a7725 Exclude tests from Sonar (#147) 2024-07-12 16:45:20 -04:00
renovate[bot]
43855ab555 [deps]: Update kotlin (#142) 2024-07-08 09:47:07 -04:00
renovate[bot]
9fdeb8e639 [deps]: Update gradle minor (#138) 2024-07-08 09:46:40 -04:00
renovate[bot]
a19523a3f8 [deps]: Lock file maintenance (#134) 2024-07-08 09:46:00 -04:00
renovate[bot]
023e5dd2fc [deps]: Update ubuntu to v22 (#133) 2024-07-08 09:45:31 -04:00
renovate[bot]
0af9bb887a [deps]: Update crowdin/github-action action to v2 (#131) 2024-07-08 09:44:58 -04:00
renovate[bot]
1e1beec6e4 [deps]: Update gh minor (#117) 2024-07-08 09:33:44 -04:00
Patrick Honkonen
9a72c0c8e5 [BWA-45] Update Androidx Lifecycle components to fix runtime crash (#140) 2024-07-01 17:09:33 -04:00
Matt Bishop
cff5c7b9a2 Adjust Sonar scan paths (#139) 2024-06-24 14:15:07 -04:00
Matt Bishop
be96f1b9d4 Remove Renovate SDK customizations (#137) 2024-06-21 11:31:36 -04:00
Matt Bishop
b8511ec4f8 Configure registry URL for SDK (#135) 2024-06-21 11:07:30 -04:00
Matt Bishop
e5f7488a7c Add Maven as supported Renovate manager (#136) 2024-06-21 10:34:00 -04:00
Patrick Honkonen
a2b7132cf3 [BWA-28] Sync translations with Crowdin (#129) 2024-06-17 12:26:17 -04:00
Patrick Honkonen
8be57a6ef7 Escape and use correct apostrophe symbol (#130) 2024-06-17 12:25:48 -04:00
renovate[bot]
49a5ff34df [deps]: Update gradle minor (#67) 2024-06-14 15:43:04 -04:00
renovate[bot]
53ac8ac49c [deps]: Update kotlin (#107) 2024-06-14 15:32:28 -04:00
renovate[bot]
2b721ac52c [deps]: Update googleProtoBufJava to v4 (major) (#118) 2024-06-14 15:19:59 -04:00
renovate[bot]
31f45ae5a4 [deps]: Update kotlin to v2 (major) (#108) 2024-06-14 15:09:01 -04:00
Patrick Honkonen
f912e00d21 [BWA-10] Sanitize launch intents before processing (#128) 2024-06-14 15:06:45 -04:00
Patrick Honkonen
7ca1eab4b2 [BWA-32] Sort verification codes by issuer (#127) 2024-06-14 15:06:15 -04:00
Patrick Honkonen
cbb469e5ab Extract account name from Aegis imports (#126) 2024-06-14 15:05:38 -04:00
Patrick Honkonen
17c9008f95 Improve 2FAS import (#124) 2024-06-13 16:02:27 -04:00
Patrick Honkonen
2640d28468 Display a descriptive error when import fails (#122) 2024-06-12 13:13:17 -05:00
Patrick Honkonen
f695254fc1 Update 2FAS import option label (#123) 2024-06-12 14:12:27 -04:00
Patrick Honkonen
18e5b711bb Fix Bitwarden import (#120) 2024-06-12 11:46:11 -04:00
Patrick Honkonen
9912123293 Remove language switcher (#121) 2024-06-12 11:38:22 -04:00
Michał Chęciński
e737f3260d [BRE-101] Remove dept-devops from CODEOWNERS (#116) 2024-06-11 18:19:32 -04:00
renovate[bot]
be8562b4db [deps]: Lock file maintenance (#109) 2024-06-11 18:19:06 -04:00
Matt Bishop
dc0413b416 Code coverage configuration (#115) 2024-06-05 16:02:24 -04:00
Patrick Honkonen
d1b5f3078e Add example test for MainViewModel (#113) 2024-06-03 13:43:13 -04:00
Matt Bishop
c8194545a4 Repo tuneup (#112) 2024-06-03 10:17:36 -04:00
Matt Bishop
c67a5d1a8d Checkmarx configuration (#111) 2024-05-31 12:31:40 -04:00
Patrick Honkonen
00b35bd3ab [BWA-16] Import Google Authenticator exports via scanner (#101) 2024-05-28 13:01:07 -04:00
renovate[bot]
a40f3b91db [deps]: Update gh minor (#106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-28 12:57:01 -04:00
Patrick Honkonen
9537bfaf6f [BWA-21] Disable screen capture (#110) 2024-05-28 12:50:30 -04:00
Patrick Honkonen
e9fb28d374 [BWA-18] Add Backup option to settings (#103) 2024-05-22 22:11:48 +00:00
Patrick Honkonen
1161c3c446 [BWA-22] Prevent FAB from hiding verification codes (#102) 2024-05-22 22:06:00 +00:00
Patrick Honkonen
d3e4b56d30 Add androidx.lifecycle dependencies to kotlin group (#104) 2024-05-22 18:04:56 -04:00
Dillon Beresford
f9dd295188 [PM-7288] Include changes to actions in scan workflow (#87) 2024-05-17 17:49:48 -05:00
Patrick Honkonen
6fae5b8b77 [BWA-12] Update workflow to accept custom inputs (#96) 2024-05-17 10:57:00 -04:00
Patrick Honkonen
2529ed3fb9 [BWA-5] Support importing Aegis exports (#99) 2024-05-17 09:53:23 -04:00
Patrick Honkonen
a1e7e92d6d [BWA-14] Support importing LastPass exports (#98) 2024-05-16 15:30:48 -04:00
Patrick Honkonen
15251c840c Separate androidxTest and androidxTestRules dependency versions (#97) 2024-05-16 15:19:55 -04:00
Patrick Honkonen
663265d7b3 [BWA-11] Import 2FAS exports (#95) 2024-05-16 12:30:53 -04:00
Patrick Honkonen
f2365771ff [BWA-9] Set task affinity to mitigate task hijacking (#94) 2024-05-15 15:13:47 -04:00
renovate[bot]
97e6758449 [deps]: Update kotlin (#90) 2024-05-15 14:50:28 -04:00
renovate[bot]
91e197db2c [deps]: Update com.google.firebase.crashlytics to v3 (#91) 2024-05-14 15:19:42 -04:00
renovate[bot]
8e10170347 [deps]: Lock file maintenance (#93) 2024-05-13 12:26:59 -04:00
renovate[bot]
74b6fc7e2e [deps]: Update com.google.firebase:firebase-bom to v33 (#92) 2024-05-13 09:29:48 -04:00
renovate[bot]
6b4f959b12 [deps]: Update gh minor (#89)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 09:19:12 -04:00
Patrick Honkonen
578efd3b7f Define issue template for bugs (#86) 2024-05-08 23:24:54 -04:00
Patrick Honkonen
31669cf457 Force scanned codes to uppercase alpha characters (#85) 2024-05-07 15:50:43 -04:00
Patrick Honkonen
d9e29d3c81 Allow users to select favorites (#84) 2024-05-07 13:21:30 -04:00
Patrick Honkonen
5c38d62a61 Allow users to import exported JSON vault files (#81) 2024-05-06 20:58:27 -04:00
Patrick Honkonen
9b5449f657 Enforce Base32 key values (#82) 2024-05-03 16:14:49 -04:00
Patrick Honkonen
6da31f797d Declare ruby version globally (#80) 2024-05-03 16:14:01 -04:00
Patrick Honkonen
f546bd2640 Fix version name generation (#78) 2024-05-02 12:28:38 -05:00
Patrick Honkonen
d32ed06516 Build release bundles and publish to Firebase (#50) 2024-05-02 10:24:37 -04:00
Patrick Honkonen
26c22295fc Use valid authenticators on Android Q and below (#77) 2024-05-01 21:20:29 -04:00
Patrick Honkonen
de9aebbda9 Remove nulab password strength library (#73) 2024-05-01 20:09:31 -05:00
Kyle Spearrin
f7ea3a7972 Fix screenshot dimensions in readme (#76) 2024-05-01 13:51:00 -04:00
Patrick Honkonen
51c3f2b04a Add README and code style guidelines (#75) 2024-05-01 09:50:36 -04:00
renovate[bot]
61d80793e7 [deps]: Update ruby to v3.3.1 (#30)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Patrick Honkonen <phonkonen@bitwarden.com>
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
2024-05-01 07:16:42 -04:00
renovate[bot]
b59814aa83 [deps]: Update kotlin (#66) 2024-04-30 18:23:21 -04:00
Patrick Honkonen
5c46e913a2 Allow users to toggle crash logging (#72) 2024-04-30 17:41:55 -04:00
Patrick Honkonen
1a592273bf Enable crashlytics (#48) 2024-04-30 17:27:44 -04:00
Patrick Honkonen
b597a2fb62 Update label for OTP type input field (#71) 2024-04-30 17:14:43 -04:00
Patrick Honkonen
ea55cbc914 Convert digits input to stepper (#70) 2024-04-29 22:19:29 -04:00
Vince Grassia
86d8a2ed8d DEVOPS-1952 - Update Gradle validation action (#69) 2024-04-29 12:33:58 -06:00
renovate[bot]
4bfe787f01 [deps]: Update gh minor (#65)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-29 09:48:36 -04:00
Patrick Honkonen
c0b04694d9 Release prep and refinement (#64) 2024-04-27 14:49:06 -04:00
Patrick Honkonen
dd4a0a502d Add About section to settings (#63) 2024-04-26 14:13:38 -04:00
Patrick Honkonen
1eb4a9770e Update empty list screen text to omit sync references (#62) 2024-04-26 14:00:05 -04:00
Patrick Honkonen
165ae77113 Allow fallback to device credential when biometrics fails (#61) 2024-04-26 12:31:03 -04:00
Patrick Honkonen
dad060e8c1 Increase spacing in tutorial slide content (#60) 2024-04-26 11:37:32 -04:00
Patrick Honkonen
75fb200eaf Remove bottom nav bar when relaunching tutorial (#59) 2024-04-26 11:17:47 -04:00
Patrick Honkonen
2bd0df2910 Adjust top app bar (#58) 2024-04-26 11:02:18 -04:00
Patrick Honkonen
2b6fe1b28a Increase spacing around Help header (#57) 2024-04-26 10:22:18 -04:00
Patrick Honkonen
f39669e615 Display correct icons when app overrides system theme. (#56) 2024-04-25 16:48:54 -05:00
Patrick Honkonen
1f0881f74e Make issuer required and account name optional (#55) 2024-04-25 17:17:15 -04:00
Patrick Honkonen
cb158b0947 Update toast message when a code is added (#53) 2024-04-25 16:48:00 -04:00
Patrick Honkonen
3adc43683a Display dark mode asset for empty vault (#54) 2024-04-25 13:40:17 -04:00
Patrick Honkonen
3ae4c72b46 Create dedicated unlock screen (#52) 2024-04-25 08:42:04 -05:00
Patrick Honkonen
7e93c1a74b Fix routing on tutorial completion (#51) 2024-04-24 19:29:48 -04:00
Patrick Honkonen
f191f02296 Allow users to enable biometric unlock from settings (#44) 2024-04-24 08:40:54 -05:00
Patrick Honkonen
9e86332c5e Cache build output separate from gradle cache (#49) 2024-04-24 10:53:21 +02:00
Patrick Honkonen
d85904748e Correct tutorial screen behavior during rotation (#45) 2024-04-24 01:23:43 -04:00
Patrick Honkonen
5167b2c491 Flip name and key fields in manual entry screen (#46) 2024-04-24 05:05:13 +00:00
Patrick Honkonen
6b88bcf02f Establish infrastructure to support biometric lock and unlock (#43) 2024-04-24 00:44:46 -04:00
Patrick Honkonen
eb97a33e8e Define proguard rules for release builds (#47) 2024-04-24 00:03:04 -04:00
Patrick Honkonen
e829e1e2ca Assemble, sign, upload and publish release builds (#38) 2024-04-22 13:49:16 -04:00
Patrick Honkonen
015cbdd37a Prompt for camera permission from Add Code button (#42) 2024-04-22 10:38:14 -04:00
Patrick Honkonen
66d834c7e9 Users can export unencrypted data to JSON or CSV (#41) 2024-04-22 10:30:04 -04:00
Patrick Honkonen
066d5c5628 Remove Import button from empty item listing screen (#40) 2024-04-22 10:29:03 -04:00
Patrick Honkonen
34c547a431 Remove Sync with Bitwarden button (#39) 2024-04-21 22:07:58 -04:00
Patrick Honkonen
af1ab4c953 Remove x8bit references from project (#37) 2024-04-17 08:34:54 -04:00
Patrick Honkonen
186766960f Include app database in auto backup and restore (#36) 2024-04-16 23:29:20 -04:00
Patrick Honkonen
de02a6999d Prompt for camera permissions from item listing screen (#34) 2024-04-16 22:19:51 -05:00
Patrick Honkonen
736761ee93 Update settings tutorial label (#35) 2024-04-16 22:19:36 -05:00
Patrick Honkonen
7bad184849 Box in list item context menu (#33) 2024-04-16 09:04:42 -05:00
Patrick Honkonen
612c8e8aa3 Implement context menu on item long press (#31) 2024-04-15 20:08:26 -05:00
Patrick Honkonen
8da98f95e1 Copy auth code on item click (#28) 2024-04-15 19:39:45 -05:00
Patrick Honkonen
6d4df646af Allow backup & restore to cloud and device transfer (#32) 2024-04-15 20:18:48 -04:00
renovate[bot]
d98db6ee67 [deps]: Update gh minor (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 16:10:17 -04:00
Patrick Honkonen
12e5314c61 Show Welcome Tutorial on first launch (#27) 2024-04-15 10:12:58 -04:00
Patrick Honkonen
b6a165aef5 Allow modification of appearance settings (#26) 2024-04-14 17:03:34 -04:00
Patrick Honkonen
6b025832d7 DIsplay base settings screen (#25) 2024-04-13 20:39:03 -04:00
Patrick Honkonen
ea58313e31 Display bottom app bar for navigation (#24) 2024-04-13 18:06:20 -04:00
Patrick Honkonen
a27002af0f Allow users to search items (#23) 2024-04-12 11:34:13 -05:00
Patrick Honkonen
8ad7c184f1 Remove View Item screen (#22) 2024-04-11 10:08:26 -04:00
Patrick Honkonen
168e662a38 Update item listing header text (#21) 2024-04-11 10:03:16 -04:00
Patrick Honkonen
a00452faf1 Edit item UI (#20) 2024-04-11 09:14:11 -04:00
Patrick Honkonen
32b0c90f17 Refactor primary data model (#19) 2024-04-09 10:01:57 -04:00
Patrick Honkonen
cd992a6994 Allow users to save items to local storage (#18) 2024-04-05 15:12:25 -05:00
renovate[bot]
38a92042de [deps]: Update kotlin (#12) 2024-04-05 13:11:17 -04:00
renovate[bot]
65263ebad9 [deps]: Update gradle minor (#11) 2024-04-05 13:03:53 -04:00
Patrick Honkonen
50530b57c6 Define local database (#17) 2024-04-05 12:49:04 -04:00
Patrick Honkonen
04a565e5a2 Include Compose compiler in kotlin renovate group (#16) 2024-04-03 18:11:20 -05:00
Patrick Honkonen
9aa091818d Display no items content when user has no items saved (#13) 2024-04-03 13:10:36 -04:00
Patrick Honkonen
6a226f21bd Correct renovate ruby manager declaration to use bundler (#15) 2024-04-03 12:39:30 -04:00
Patrick Honkonen
67d641572e Trigger checks during CI execution (#10) 2024-04-02 22:35:00 -05:00
Patrick Honkonen
a92ed17d65 QR Scanner and Manual Key Entry screens (#7) 2024-04-02 17:45:33 -04:00
Patrick Honkonen
580a0eecdd Configure Renovate to monitor Android project dependencies (#9) 2024-04-02 16:19:38 -05:00
Patrick Honkonen
d28a754ee9 Introduce Expandable floating action button (#6) 2024-04-02 10:34:13 -04:00
Matt Bishop
5fd8593095 Provide prefix for Renovate 2024-03-29 11:30:47 -04:00
Patrick Honkonen
bcaf00dc97 Change color of countdown indicator when period nears expiration (#5) 2024-03-29 00:02:01 -04:00
Matt Bishop
c012e3cb7e Disable SARIF upload until repo becomes public (#8) 2024-03-28 11:05:03 -04:00
Patrick Honkonen
4575f52fe0 Splash, Item Listing, & View Item screens (#4) 2024-03-28 09:27:33 -04:00
Matt Bishop
c01a101f83 Adjust scan permissions 2024-03-27 10:02:59 -04:00
Patrick Honkonen
8553fb3995 Set mobile dev team as default code owners (#3) 2024-03-27 10:31:20 +01:00
Patrick Honkonen
3636da5c4e Update .gitignore (#2) 2024-03-26 14:22:54 -04:00
Matt Bishop
8bd430b793 Initial commit 2024-03-26 12:09:32 -04:00
1249 changed files with 89051 additions and 17904 deletions

View File

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

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 @dseverns-livefront @ahaisting-livefront
* @bitwarden/team-android @brian-livefront @david-livefront @dseverns-livefront @ahaisting-livefront @phil-livefront
# Actions and workflow changes.
.github/workflows @bitwarden/dept-development-mobile
.github/ @bitwarden/dept-development-mobile
# Auth
# app/src/main/java/com/x8bit/bitwarden/data/auth @bitwarden/team-auth-dev

84
.github/ISSUE_TEMPLATE/bug-bwa.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
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,6 +1,6 @@
name: Android Bug Report
name: Password Manager Android App Bug Report
description: File a bug report
labels: [ bug ]
labels: [ "app:password-manager", "bug" ]
body:
- type: markdown
attributes:

View File

@@ -1,8 +1,5 @@
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.
@@ -15,3 +12,5 @@ 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,10 +15,11 @@
- 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 or informed the documentation team
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team
## 🦮 Reviewer guidelines
@@ -27,8 +28,7 @@
- 👍 (`:+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 Normal file
View File

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

View File

@@ -18,6 +18,7 @@
"description": "Kotlin and Compose dependencies that must be updated together to maintain compatibility.",
"matchPackagePatterns": [
"androidx.compose:compose-bom",
"androidx.lifecycle:*",
"org.jetbrains.kotlin.*",
"com.google.devtools.ksp"
],

View File

@@ -0,0 +1,289 @@
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 checkAuthenticator
- 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@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Cache Gradle files
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
@@ -53,7 +53,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
${{ github.workspace }}/build-cache
@@ -62,13 +62,13 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
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@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.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@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Cache Gradle files
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
@@ -170,7 +170,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
${{ github.workspace }}/build-cache
@@ -179,11 +179,20 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.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))
@@ -244,78 +253,78 @@ jobs:
- name: Upload release Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.aab
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab
if-no-files-found: error
- name: Upload beta Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta.aab
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab
if-no-files-found: error
- name: Upload release .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.apk
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
if-no-files-found: error
- name: Upload beta .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta.apk
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.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-standard-release.apk" \
sha256sum "app/build/outputs/apk/standard/release/com.x8bit.bitwarden.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-standard-beta.apk" \
sha256sum "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.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-standard-release.aab" \
sha256sum "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.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-standard-beta.aab" \
sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.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-standard-debug.apk" \
sha256sum "app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.apk-sha256.txt
path: ./com.x8bit.bitwarden.apk-sha256.txt
@@ -323,7 +332,7 @@ jobs:
- name: Upload .apk SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta.apk-sha256.txt
path: ./com.x8bit.bitwarden.beta.apk-sha256.txt
@@ -331,7 +340,7 @@ jobs:
- name: Upload .aab SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.aab-sha256.txt
path: ./com.x8bit.bitwarden.aab-sha256.txt
@@ -339,7 +348,7 @@ jobs:
- name: Upload .aab SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta.aab-sha256.txt
path: ./com.x8bit.bitwarden.beta.aab-sha256.txt
@@ -347,7 +356,7 @@ jobs:
- name: Upload .apk SHA file for debug
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
@@ -396,7 +405,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
@@ -433,10 +442,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@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Cache Gradle files
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
@@ -446,7 +455,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
${{ github.workspace }}/build-cache
@@ -455,11 +464,20 @@ jobs:
${{ runner.os }}-build-
- name: Configure JDK
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.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: |
@@ -497,38 +515,38 @@ jobs:
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden-fdroid.apk
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.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-release.apk" \
sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk" \
> ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
- name: Upload F-Droid SHA file
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta-fdroid.apk
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden-fdroid-beta.apk
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.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-fdroid-beta.apk" \
sha256sum "app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.apk" \
> ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
- name: Upload F-Droid Beta SHA file
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
path: ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt

View File

@@ -0,0 +1,56 @@
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,10 +28,17 @@ 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@2d540f18b0a416b1fbf2ee5be35841bd380fc1da # v2.3.0
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
with:
config: crowdin.yml

View File

@@ -0,0 +1,30 @@
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@2bd1450c2cdb2a8ac886232b8589696f22794229 # v0.2.0
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token"
- name: Upload sources
uses: crowdin/github-action@2d540f18b0a416b1fbf2ee5be35841bd380fc1da # v2.3.0
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
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,7 +22,8 @@ on:
prerelease:
description: 'Mark as pre-release'
type: boolean
make_latest:
default: true
make-latest:
description: 'Set as the latest release'
type: boolean
branch-protection-type:
@@ -36,6 +37,7 @@ env:
ARTIFACTS_PATH: artifacts
jobs:
create-release:
name: Create GitHub Release
runs-on: ubuntu-24.04
permissions:
contents: write
@@ -43,7 +45,7 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
@@ -51,7 +53,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)
@@ -81,7 +83,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)
@@ -93,13 +95,13 @@ jobs:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
tag_name: ${{ inputs.version-name }}
name: "v${{ inputs.version-name }} (${{ inputs.version-number }})"
tag_name: "v${{ inputs.version-name }}"
name: "${{ 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: |
@@ -110,7 +112,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,26 +10,23 @@ 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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Create RC Branch
if: inputs.release_type == 'RC'
env:
RC_PREFIX_DATE: ${{ inputs.rc_prefix_date }}
RC_PREFIX_DATE: "true" # replace with input if needed
run: |
if [ "$RC_PREFIX_DATE" = "true" ]; then
current_date=$(date +'%Y.%m')
@@ -45,12 +42,17 @@ jobs:
- name: Create Hotfix Branch
if: inputs.release_type == 'Hotfix'
run: |
latest_tag=$(git describe --tags --abbrev=0)
latest_tag=$(git tag -l --sort=-creatordate | head -n 1)
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

@@ -0,0 +1,76 @@
name: Scan Authenticator
on:
workflow_dispatch:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
pull_request_target:
types: [opened, synchronize]
jobs:
check-run:
name: Check PR run
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
sast:
name: SAST scan
runs-on: ubuntu-24.04
needs: check-run
permissions:
contents: read
pull-requests: write
security-events: write
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
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 . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
with:
sarif_file: cx_result.sarif
quality:
name: Quality scan
runs-on: ubuntu-24.04
needs: check-run
permissions:
contents: read
pull-requests: write
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- 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 }}
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}

61
.github/workflows/scan-ci.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
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 }}
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}

View File

@@ -1,16 +1,9 @@
name: Scan
name: Scan Pull Requests
on:
workflow_dispatch:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
pull_request_target:
types: [opened, synchronize]
merge_group:
types: [checks_requested]
jobs:
check-run:
@@ -33,7 +26,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@03a90e7253dadd7e2fff55f5dfbce647b39040a1 # 2.0.37
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
@@ -48,7 +41,7 @@ jobs:
--output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@9278e421667d5d90a2839487a482448c4ec7df4d # v3.27.2
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
with:
sarif_file: cx_result.sarif
@@ -68,11 +61,11 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with SonarCloud
uses: sonarsource/sonarcloud-github-action@383f7e52eae3ab0510c3cb0e7d9d150bbaeab838 # v3.1.0
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
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

@@ -0,0 +1,82 @@
name: Test Authenticator
on:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
pull_request_target:
types: [opened, synchronize]
env:
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
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
- 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 Ruby
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
- name: Configure JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Install Fastlane
run: |
gem install bundler:2.2.27
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Build and test Authenticator
run: |
bundle exec fastlane checkAuthenticator
- name: Upload to codecov.io
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
with:
files: authenticator/build/reports/kover/reportDebug.xml

View File

@@ -6,42 +6,33 @@ on:
- "main"
- "rc"
- "hotfix-rc"
pull_request_target:
pull_request:
types: [opened, synchronize]
merge_group:
type: [checks_requested]
workflow_dispatch:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVA_VERSION: 17
_JAVA_VERSION: 17
_GITHUB_ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
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@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Cache Gradle files
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
@@ -51,7 +42,7 @@ jobs:
${{ runner.os }}-gradle-v2-
- name: Cache build output
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
${{ github.workspace }}/build-cache
@@ -60,15 +51,15 @@ jobs:
${{ runner.os }}-build-
- name: Configure Ruby
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
- name: Configure JDK
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
java-version: ${{ env._JAVA_VERSION }}
- name: Install Fastlane
run: |
@@ -77,19 +68,58 @@ 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 on failure
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
if: failure()
- name: Upload test reports
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
if: always()
with:
name: test-reports
path: |
app/build/reports/tests/
app/build/reports/kover/reportStandardDebug.xml
report:
name: Process Test Reports
needs: test
runs-on: ubuntu-24.04
permissions:
contents: read
issues: write
pull-requests: write
if: success()
steps:
- name: Download test artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
if: github.event_name == 'push' || github.event_name == 'pull_request'
with:
name: test-reports
path: app/build/reports/tests/
- name: Upload to codecov.io
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
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
with:
file: app/build/reports/kover/reportStandardDebug.xml
os: linux
files: kover/reportStandardDebug.xml
fail_ci_if_error: true
- name: Comment PR if tests failed
if: steps.upload-to-codecov.outcome == 'failure' && (github.event_name == 'push' || github.event_name == 'pull_request')
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
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

3
.gitignore vendored
View File

@@ -25,5 +25,6 @@ user.properties
# Secrets
/keystores/*.jks
/app/src/standardDebug/google-services.json
/app/src/standardBeta/google-services.json
/app/src/standardRelease/google-services.json
/authenticator/src/google-services.json

1
.husky/pre-commit Executable file
View File

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

View File

@@ -10,20 +10,20 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.1003.0)
aws-sdk-core (3.212.0)
aws-partitions (1.1040.0)
aws-sdk-core (3.216.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.95.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (1.97.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.170.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-s3 (1.178.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.1)
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.2.0)
@@ -32,7 +32,7 @@ GEM
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
date (3.4.0)
date (3.4.1)
declarative (0.0.20)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
@@ -59,8 +59,8 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
@@ -68,8 +68,8 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.225.0)
fastimage (2.4.0)
fastlane (2.226.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -109,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.3.0)
xcpretty (~> 0.4.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-firebase_app_distribution (0.9.1)
fastlane-plugin-firebase_app_distribution (0.10.0)
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
fastlane-sirp (1.0.0)
@@ -158,12 +158,12 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.7)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.8.1)
jwt (2.9.3)
json (2.9.1)
jwt (2.10.1)
base64
mini_magick (4.13.2)
mini_mime (1.1.5)
@@ -174,7 +174,7 @@ GEM
nkf (0.2.0)
optparse (0.6.0)
os (1.1.4)
plist (3.7.1)
plist (3.7.2)
public_suffix (6.0.1)
rake (13.2.1)
representable (3.2.0)
@@ -182,10 +182,10 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.9)
rouge (2.0.7)
rexml (3.4.0)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
rubyzip (2.4.1)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
@@ -216,8 +216,8 @@ GEM
colored2 (~> 3.1)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty (0.4.0)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)

61
README-bwa.md Normal file
View File

@@ -0,0 +1,61 @@
[![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

@@ -132,6 +132,11 @@ The following is a list of all third-party dependencies included as part of the
- 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
- **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
- **Glide**
- https://github.com/bumptech/glide

View File

@@ -1,21 +1,32 @@
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,6 +1,9 @@
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
@@ -32,6 +35,16 @@ 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,6 +64,12 @@ android {
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField(
type = "String",
name = "CI_INFO",
value = "${ciProperties.getOrDefault("ci.info", "\"local\"")}"
)
}
androidResources {
@@ -115,6 +134,39 @@ 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())
@@ -216,6 +268,7 @@ dependencies {
standardImplementation(libs.google.firebase.cloud.messaging)
standardImplementation(platform(libs.google.firebase.bom))
standardImplementation(libs.google.firebase.crashlytics)
standardImplementation(libs.google.play.review)
testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.google.hilt.android.testing)
@@ -272,6 +325,7 @@ kover {
"*_*Factory\$*",
"*.Hilt_*",
"*_HiltModules",
"*_HiltModules*",
"*_HiltModules\$*",
"*_Impl",
"*_Impl\$*",
@@ -298,6 +352,10 @@ tasks {
dependsOn("detekt")
}
getByName("sonar") {
dependsOn("check")
}
withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
jvmTarget = libs.versions.jvmTarget.get()
}
@@ -310,15 +368,16 @@ tasks {
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 tasks = tasks.withType<GoogleServicesTask>() +
val fdroidTasksToDisable = tasks.withType<GoogleServicesTask>() +
tasks.withType<InjectMappingFileIdTask>() +
tasks.withType<UploadMappingFileTask>()
tasks
fdroidTasksToDisable
.filter { it.name.contains("Fdroid") }
.forEach { it.enabled = false }
}
@@ -335,8 +394,17 @@ sonar {
}
}
tasks {
getByName("sonar") {
dependsOn("check")
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")
}
}

View File

@@ -0,0 +1,12 @@
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,6 +320,11 @@
<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,5 +1,31 @@
{
"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": {
"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"
}
]
}
},
{
"type": "android",
"info": {
@@ -24,6 +50,30 @@
]
}
},
{
"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": {
"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": {
@@ -35,46 +85,6 @@
}
]
}
},
{
"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

@@ -4,8 +4,8 @@ import android.app.Application
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

@@ -11,15 +11,16 @@ 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.rememberNavController
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.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.manager.util.ObserveScreenDataEffect
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
@@ -39,9 +40,6 @@ class MainActivity : AppCompatActivity() {
private val mainViewModel: MainViewModel by viewModels()
@Inject
lateinit var accessibilityActivityManager: AccessibilityActivityManager
@Inject
lateinit var autofillActivityManager: AutofillActivityManager
@@ -57,6 +55,7 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var debugLaunchManager: DebugMenuLaunchManager
@Suppress("LongMethod")
override fun onCreate(savedInstanceState: Bundle?) {
var shouldShowSplashScreen = true
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
@@ -70,13 +69,14 @@ class MainActivity : AppCompatActivity() {
)
}
// 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.
// Within the app the language and theme will change dynamically and will be managed by the
// OS, but we need to ensure we properly set the values when upgrading from older versions
// that handle this differently or when the activity restarts.
settingsRepository.appLanguage.localeName?.let { localeName ->
val localeList = LocaleListCompat.forLanguageTags(localeName)
AppCompatDelegate.setApplicationLocales(localeList)
}
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
setContent {
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
val navController = rememberNavController()
@@ -98,10 +98,29 @@ 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 {
ObserveScreenDataEffect(
onDataUpdate = remember(mainViewModel) {
{
mainViewModel.trySendAction(
MainAction.ResumeScreenDataReceived(it),
)
}
},
)
BitwardenTheme(theme = state.theme) {
RootNavScreen(
onSplashScreenRemoved = { shouldShowSplashScreen = false },

View File

@@ -13,13 +13,15 @@ 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.getFido2CredentialRequestOrNull
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CreateCredentialRequestOrNull
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.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.SpecialCircumstance
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
@@ -71,6 +73,7 @@ class MainViewModel @Inject constructor(
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(
@@ -108,6 +111,11 @@ 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
@@ -180,6 +188,14 @@ class MainViewModel @Inject constructor(
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
is MainAction.ResumeScreenDataReceived -> handleAppResumeDataUpdated(action)
}
}
private fun handleAppResumeDataUpdated(action: MainAction.ResumeScreenDataReceived) {
when (val data = action.screenResumeData) {
null -> appResumeManager.clearResumeScreen()
else -> appResumeManager.setResumeScreen(data)
}
}
@@ -211,6 +227,7 @@ 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() {
@@ -257,7 +274,7 @@ class MainViewModel @Inject constructor(
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
val hasVaultShortcut = intent.isMyVaultShortcut
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
val fido2CreateCredentialRequestData = intent.getFido2CreateCredentialRequestOrNull()
val completeRegistrationData = intent.getCompleteRegistrationDataIntentOrNull()
val fido2CredentialAssertionRequest = intent.getFido2AssertionRequestOrNull()
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
@@ -318,25 +335,31 @@ class MainViewModel @Inject constructor(
)
}
fido2CredentialRequestData != null -> {
fido2CreateCredentialRequestData != 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.
fido2CredentialManager.isUserVerified = false
fido2CreateCredentialRequestData.isUserVerified
?.let { isVerified -> fido2CredentialManager.isUserVerified = isVerified }
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Save(
fido2CredentialRequest = fido2CredentialRequestData,
fido2CreateCredentialRequest = fido2CreateCredentialRequestData,
)
// Switch accounts if the selected user is not the active user.
if (authRepository.activeUserId != null &&
authRepository.activeUserId != fido2CredentialRequestData.userId
authRepository.activeUserId != fido2CreateCredentialRequestData.userId
) {
authRepository.switchAccount(fido2CredentialRequestData.userId)
authRepository.switchAccount(fido2CreateCredentialRequestData.userId)
}
}
fido2CredentialAssertionRequest != 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.
fido2CredentialAssertionRequest.isUserVerified
?.let { isVerified -> fido2CredentialManager.isUserVerified = isVerified }
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.Fido2Assertion(
fido2AssertionRequest = fido2CredentialAssertionRequest,
@@ -443,6 +466,11 @@ sealed class MainAction {
*/
data object OpenDebugMenu : MainAction()
/**
* Receive event to save the app resume screen
*/
data class ResumeScreenDataReceived(val screenResumeData: AppResumeScreenData?) : MainAction()
/**
* Actions for internal use by the ViewModel.
*/
@@ -518,4 +546,18 @@ 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,11 +1,13 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
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.
@@ -171,6 +173,16 @@ 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].
*/
@@ -328,7 +340,27 @@ 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 new device notice state for the given [userId].
*/
fun getNewDeviceNoticeState(userId: String): NewDeviceNoticeState
/**
* Stores the new device notice state for the given [userId].
*/
fun storeNewDeviceNoticeState(userId: String, newState: NewDeviceNoticeState?)
/**
* 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

@@ -2,6 +2,8 @@ package com.x8bit.bitwarden.data.auth.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
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
@@ -13,14 +15,15 @@ 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"
@@ -46,6 +49,8 @@ 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 NEW_DEVICE_NOTICE_STATE = "newDeviceNoticeState"
private const val LAST_LOCK_TIMESTAMP = "lastLockTimestamp"
/**
* Primary implementation of [AuthDiskSource].
@@ -142,6 +147,7 @@ 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)
@@ -150,6 +156,7 @@ 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.
@@ -277,6 +284,17 @@ 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))
@@ -471,6 +489,35 @@ class AuthDiskSourceImpl(
getMutableShowImportLoginsFlow(userId)
.onSubscription { emit(getShowImportLogins(userId)) }
override fun getNewDeviceNoticeState(userId: String): NewDeviceNoticeState {
return getString(key = NEW_DEVICE_NOTICE_STATE.appendIdentifier(userId))?.let {
json.decodeFromStringOrNull(it)
} ?: NewDeviceNoticeState(
displayStatus = NewDeviceNoticeDisplayStatus.HAS_NOT_SEEN,
lastSeenDate = null,
)
}
override fun storeNewDeviceNoticeState(userId: String, newState: NewDeviceNoticeState?) {
putString(
key = NEW_DEVICE_NOTICE_STATE.appendIdentifier(userId),
value = newState?.let { json.encodeToString(it) },
)
}
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

@@ -2,10 +2,12 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.model
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
import kotlinx.serialization.Contextual
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
import java.time.ZonedDateTime
/**
* Represents the current account information for a given user.
@@ -35,6 +37,7 @@ 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).
@@ -46,6 +49,7 @@ 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
@@ -59,6 +63,9 @@ data class AccountJson(
@SerialName("emailVerified")
val isEmailVerified: Boolean?,
@SerialName("isTwoFactorEnabled")
val isTwoFactorEnabled: Boolean?,
@SerialName("name")
val name: String?,
@@ -92,6 +99,10 @@ data class AccountJson(
@SerialName("userDecryptionOptions")
@JsonNames("accountDecryptionOptions")
val userDecryptionOptions: UserDecryptionOptionsJson?,
@SerialName("creationDate")
@Contextual
val creationDate: ZonedDateTime?,
)
/**

View File

@@ -7,6 +7,7 @@ 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).
@@ -19,6 +20,9 @@ data class EnvironmentUrlDataJson(
@SerialName("base")
val base: String,
@SerialName("keyUri")
val keyUri: String? = null,
@SerialName("api")
val api: String? = null,
@@ -51,6 +55,7 @@ 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",
@@ -71,6 +76,7 @@ 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

@@ -0,0 +1,60 @@
package com.x8bit.bitwarden.data.auth.datasource.disk.model
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.ZonedDateTime
/**
* Describes the current display status of the new device notice screen.
*/
@Serializable
enum class NewDeviceNoticeDisplayStatus {
/**
* The user has seen the screen and indicated they can access their email.
*/
@SerialName("canAccessEmail")
CAN_ACCESS_EMAIL,
/**
* The user has indicated they can access their email
* as specified by the Permanent mode of the notice.
*/
@SerialName("canAccessEmailPermanent")
CAN_ACCESS_EMAIL_PERMANENT,
/**
* The user has not seen the screen.
*/
@SerialName("hasNotSeen")
HAS_NOT_SEEN,
/**
* The user has seen the screen and selected "remind me later".
*/
@SerialName("hasSeen")
HAS_SEEN,
}
/**
* The state of the new device notice screen.
*/
@Suppress("MagicNumber")
@Serializable
data class NewDeviceNoticeState(
@SerialName("displayStatus")
val displayStatus: NewDeviceNoticeDisplayStatus,
@SerialName("lastSeenDate")
@Contextual
val lastSeenDate: ZonedDateTime?,
) {
/**
* Whether the [lastSeenDate] is at least 7 days old.
*/
val shouldDisplayNoticeIfSeen = lastSeenDate
?.isBefore(
ZonedDateTime.now().minusDays(7),
)
?: false
}

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
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.auth.datasource.network.model.ResendNewDeviceOtpRequestJson
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
@@ -28,4 +29,9 @@ 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

@@ -47,12 +47,13 @@ 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>
): NetworkResult<PrevalidateSsoResponseJson.Success>
/**
* This call needs to be synchronous so we need it to return a [Call] directly. The identity

View File

@@ -21,5 +21,7 @@ enum class AuthRequestTypeJson {
}
@Keep
private class AuthRequestTypeSerializer :
BaseEnumeratedIntSerializer<AuthRequestTypeJson>(AuthRequestTypeJson.entries.toTypedArray())
private class AuthRequestTypeSerializer : BaseEnumeratedIntSerializer<AuthRequestTypeJson>(
className = "AuthRequestTypeJson",
values = AuthRequestTypeJson.entries.toTypedArray(),
)

View File

@@ -1,7 +1,9 @@
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
/**
@@ -92,41 +94,56 @@ 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")
val errorModel: ErrorModel?,
@SerialName("errorModel")
val legacyErrorModel: LegacyErrorModel?,
private val errorModel: ErrorModel?,
) : GetTokenResponseJson() {
/**
* The error message returned from the server, or null.
*/
val errorMessage: String?
get() = errorModel?.errorMessage ?: legacyErrorModel?.errorMessage
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
}
/**
* 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

@@ -18,5 +18,7 @@ enum class KdfTypeJson {
}
@Keep
private class KdfTypeSerializer :
BaseEnumeratedIntSerializer<KdfTypeJson>(KdfTypeJson.entries.toTypedArray())
private class KdfTypeSerializer : BaseEnumeratedIntSerializer<KdfTypeJson>(
className = "KdfTypeJson",
values = KdfTypeJson.entries.toTypedArray(),
)

View File

@@ -7,6 +7,20 @@ import kotlinx.serialization.Serializable
* Response body from the SSO prevalidate request.
*/
@Serializable
data class PrevalidateSsoResponseJson(
@SerialName("token") val token: String?,
)
sealed class PrevalidateSsoResponseJson {
/**
* Models json body of a successful response.
*/
@Serializable
data class Success(
@SerialName("token") val token: String?,
) : PrevalidateSsoResponseJson()
/**
* Models json body of an error response.
*/
@Serializable
data class Error(
@SerialName("message") val message: String?,
) : PrevalidateSsoResponseJson()
}

View File

@@ -1,7 +1,9 @@
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.
@@ -50,20 +52,24 @@ 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(
@SerialName("message")
@JsonNames("message")
@SerialName("Message")
private val invalidMessage: String? = null,
@SerialName("Message")
private val errorMessage: String? = null,
@SerialName("validationErrors")
val validationErrors: Map<String, List<String>>?,
private val validationErrors: Map<String, List<String>>?,
) : RegisterResponseJson() {
/**
* A generic error message.
*/
val message: String? get() = invalidMessage ?: errorMessage
val message: String?
get() = validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
?: invalidMessage
}
}

View File

@@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Hold the information necessary to resend the email with the
* new device verification code.
*
* @property email The user's email address.
* @property passwordHash The master password hash
*/
@Serializable
data class ResendNewDeviceOtpRequestJson(
@SerialName("Email")
val email: String,
@SerialName("MasterPasswordHash")
val passwordHash: String?,
)

View File

@@ -39,5 +39,7 @@ enum class TwoFactorAuthMethod(val value: UInt) {
}
@Keep
private class TwoFactorAuthMethodSerializer :
BaseEnumeratedIntSerializer<TwoFactorAuthMethod>(TwoFactorAuthMethod.entries.toTypedArray())
private class TwoFactorAuthMethodSerializer : BaseEnumeratedIntSerializer<TwoFactorAuthMethod>(
className = "TwoFactorAuthMethod",
values = TwoFactorAuthMethod.entries.toTypedArray(),
)

View File

@@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyReq
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendNewDeviceOtpRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
@@ -52,6 +53,11 @@ interface AccountsService {
*/
suspend fun resendVerificationCodeEmail(body: ResendEmailRequestJson): Result<Unit>
/**
* Resend the email with the verification code for new devices
*/
suspend fun resendNewDeviceOtp(body: ResendNewDeviceOtpRequestJson): Result<Unit>
/**
* Reset the password.
*/

View File

@@ -13,11 +13,13 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMaster
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendNewDeviceOtpRequestJson
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.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX
import com.x8bit.bitwarden.data.platform.datasource.network.util.NetworkErrorCode
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import kotlinx.serialization.json.Json
@@ -72,7 +74,7 @@ class AccountsServiceImpl(
throwable
.toBitwardenError()
.parseErrorBodyOrNull<DeleteAccountResponseJson.Invalid>(
code = 400,
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: throw throwable
@@ -103,7 +105,7 @@ class AccountsServiceImpl(
throwable
.toBitwardenError()
.parseErrorBodyOrNull<PasswordHintResponseJson.Error>(
code = 429,
code = NetworkErrorCode.TOO_MANY_REQUESTS,
json = json,
)
?: throw throwable
@@ -114,6 +116,11 @@ class AccountsServiceImpl(
.resendVerificationCodeEmail(body = body)
.toResult()
override suspend fun resendNewDeviceOtp(body: ResendNewDeviceOtpRequestJson): Result<Unit> =
unauthenticatedAccountsApi
.resendNewDeviceOtp(body = body)
.toResult()
override suspend fun resetPassword(body: ResetPasswordRequestJson): Result<Unit> =
if (body.currentPasswordHash == null) {
authenticatedAccountsApi

View File

@@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequ
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.SendVerificationEmailResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
@@ -45,6 +46,7 @@ interface IdentityService {
authModel: IdentityTokenAuthModel,
captchaToken: String?,
twoFactorData: TwoFactorDataModel? = null,
newDeviceOtp: String? = null,
): Result<GetTokenResponseJson>
/**
@@ -68,7 +70,7 @@ interface IdentityService {
*/
suspend fun sendVerificationEmail(
body: SendVerificationEmailRequestJson,
): Result<String?>
): Result<SendVerificationEmailResponseJson>
/**
* Register a new account to Bitwarden using email verification flow.

View File

@@ -11,10 +11,12 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequ
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.SendVerificationEmailResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.NetworkErrorCode
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForNetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
@@ -33,7 +35,6 @@ class IdentityServiceImpl(
.preLogin(PreLoginRequestJson(email = email))
.toResult()
@Suppress("MagicNumber")
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
unauthenticatedIdentityApi
.register(body)
@@ -42,23 +43,26 @@ class IdentityServiceImpl(
val bitwardenError = throwable.toBitwardenError()
bitwardenError
.parseErrorBodyOrNull<RegisterResponseJson.CaptchaRequired>(
code = 400,
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
codes = listOf(400, 429),
codes = listOf(
NetworkErrorCode.BAD_REQUEST,
NetworkErrorCode.TOO_MANY_REQUESTS,
),
json = json,
)
?: throw throwable
}
@Suppress("MagicNumber")
override suspend fun getToken(
uniqueAppId: String,
email: String,
authModel: IdentityTokenAuthModel,
captchaToken: String?,
twoFactorData: TwoFactorDataModel?,
newDeviceOtp: String?,
): Result<GetTokenResponseJson> = unauthenticatedIdentityApi
.getToken(
scope = "api offline_access",
@@ -78,20 +82,25 @@ class IdentityServiceImpl(
twoFactorRemember = twoFactorData?.remember?.let { if (it) "1" else "0 " },
captchaResponse = captchaToken,
authRequestId = authModel.authRequestId,
newDeviceOtp = newDeviceOtp,
)
.toResult()
.recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError()
bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>(
code = 400,
json = json,
) ?: bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.TwoFactorRequired>(
code = 400,
json = json,
) ?: bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.Invalid>(
code = 400,
json = json,
) ?: throw throwable
bitwardenError
.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.TwoFactorRequired>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.Invalid>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: throw throwable
}
override suspend fun prevalidateSso(
@@ -101,6 +110,15 @@ class IdentityServiceImpl(
organizationIdentifier = organizationIdentifier,
)
.toResult()
.recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError()
bitwardenError
.parseErrorBodyOrNull<PrevalidateSsoResponseJson.Error>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: throw throwable
}
override fun refreshTokenSynchronously(
refreshToken: String,
@@ -113,7 +131,6 @@ class IdentityServiceImpl(
.executeForNetworkResult()
.toResult()
@Suppress("MagicNumber")
override suspend fun registerFinish(
body: RegisterFinishRequestJson,
): Result<RegisterResponseJson> =
@@ -124,7 +141,10 @@ class IdentityServiceImpl(
val bitwardenError = throwable.toBitwardenError()
bitwardenError
.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
codes = listOf(400, 429),
codes = listOf(
NetworkErrorCode.BAD_REQUEST,
NetworkErrorCode.TOO_MANY_REQUESTS,
),
json = json,
)
?: throw throwable
@@ -132,11 +152,20 @@ class IdentityServiceImpl(
override suspend fun sendVerificationEmail(
body: SendVerificationEmailRequestJson,
): Result<String?> {
): Result<SendVerificationEmailResponseJson> {
return unauthenticatedIdentityApi
.sendVerificationEmail(body = body)
.toResult()
.map { it?.content }
.map { SendVerificationEmailResponseJson.Success(it?.content) }
.recoverCatching { throwable ->
throwable
.toBitwardenError()
.parseErrorBodyOrNull<SendVerificationEmailResponseJson.Invalid>(
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: throw throwable
}
}
override suspend fun verifyEmailRegistrationToken(
@@ -151,7 +180,7 @@ class IdentityServiceImpl(
val bitwardenError = throwable.toBitwardenError()
bitwardenError
.parseErrorBodyOrNull<VerifyEmailTokenResponseJson.Invalid>(
code = 400,
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?.checkForExpiredMessage()

View File

@@ -8,7 +8,7 @@ import com.bitwarden.core.RegisterKeyResponse
import com.bitwarden.core.RegisterTdeKeyResponse
import com.bitwarden.crypto.HashPurpose
import com.bitwarden.crypto.Kdf
import com.bitwarden.sdk.ClientAuth
import com.bitwarden.sdk.AuthClient
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toPasswordStrengthOrNull
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte
@@ -17,7 +17,7 @@ import com.x8bit.bitwarden.data.platform.manager.SdkClientManager
/**
* Primary implementation of [AuthSdkSource] that serves as a convenience wrapper around a
* [ClientAuth].
* [AuthClient].
*/
class AuthSdkSourceImpl(
sdkClientManager: SdkClientManager,

View File

@@ -55,5 +55,5 @@ class TrustedDeviceManagerImpl(
authDiskSource.storeIsTdeLoginComplete(userId = userId, isTdeLoginComplete = true)
}
.also { authDiskSource.storeShouldTrustDevice(userId = userId, shouldTrustDevice = null) }
.map { Unit }
.map { }
}

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.auth.repository
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
@@ -229,6 +230,19 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
organizationIdentifier: String,
): LoginResult
/**
* Repeat the previous login attempt but this time with New Device OTP
* information. Password is included if available to unlock the vault after
* authentication. Updated access token will be reflected in [authStateFlow].
*/
suspend fun login(
email: String,
password: String?,
newDeviceOtp: String,
captchaToken: String?,
orgIdentifier: String?,
): LoginResult
/**
* Log out the current user.
*/
@@ -251,6 +265,11 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
*/
suspend fun resendVerificationCodeEmail(): ResendEmailResult
/**
* Resend the email with the new device verification code.
*/
suspend fun resendNewDeviceOtp(): ResendEmailResult
/**
* Switches to the account corresponding to the given [userId] if possible.
*/
@@ -361,8 +380,10 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
/**
* Get the password strength for the given [email] and [password] combo.
* If no value is passed for the [email] will use the active email of the current active
* account via the [userStateFlow].
*/
suspend fun getPasswordStrength(email: String, password: String): PasswordStrengthResult
suspend fun getPasswordStrength(email: String? = null, password: String): PasswordStrengthResult
/**
* Validates the master password for the current logged in user.
@@ -400,5 +421,20 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
/**
* Update the value of the onboarding status for the user.
*/
fun setOnboardingStatus(userId: String, status: OnboardingStatus?)
fun setOnboardingStatus(status: OnboardingStatus)
/**
* Checks if a new device notice should be displayed.
*/
fun checkUserNeedsNewDeviceTwoFactorNotice(): Boolean
/**
* Gets the new device notice state of active user.
*/
fun getNewDeviceNoticeState(): NewDeviceNoticeState?
/**
* Stores the new device notice state for active user.
*/
fun setNewDeviceNoticeState(newState: NewDeviceNoticeState?)
}

View File

@@ -2,13 +2,14 @@ package com.x8bit.bitwarden.data.auth.repository
import com.bitwarden.core.AuthRequestMethod
import com.bitwarden.core.InitUserCryptoMethod
import com.bitwarden.core.InitUserCryptoRequest
import com.bitwarden.crypto.HashPurpose
import com.bitwarden.crypto.Kdf
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountResponseJson
@@ -16,13 +17,16 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
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.ResendEmailRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendNewDeviceOtpRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
@@ -94,6 +98,7 @@ import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITER
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.datasource.disk.ConfigDiskSource
import com.x8bit.bitwarden.data.platform.datasource.network.util.isSslHandShakeError
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
import com.x8bit.bitwarden.data.platform.manager.LogsManager
@@ -105,6 +110,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.platform.repository.util.toEnvironmentUrls
import com.x8bit.bitwarden.data.platform.util.asFailure
@@ -114,7 +120,6 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationType
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockError
@@ -141,6 +146,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import java.time.ZonedDateTime
import javax.inject.Singleton
/**
@@ -220,6 +226,11 @@ class AuthRepositoryImpl(
*/
private var resendEmailRequestJson: ResendEmailRequestJson? = null
/**
* The information necessary to resend the verification code email for new devices.
*/
private var resendNewDeviceOtpRequestJson: ResendNewDeviceOtpRequestJson? = null
private var organizationIdentifier: String? = null
/**
@@ -625,7 +636,12 @@ class AuthRepositoryImpl(
)
}
.fold(
onFailure = { LoginResult.Error(errorMessage = null) },
onFailure = { throwable ->
when {
throwable.isSslHandShakeError() -> LoginResult.CertificateError
else -> LoginResult.Error(errorMessage = null)
}
},
onSuccess = { it },
)
@@ -674,6 +690,26 @@ class AuthRepositoryImpl(
}
?: LoginResult.Error(errorMessage = null)
override suspend fun login(
email: String,
password: String?,
newDeviceOtp: String,
captchaToken: String?,
orgIdentifier: String?,
): LoginResult = identityTokenAuthModel
?.let {
loginCommon(
email = email,
password = password,
authModel = it,
newDeviceOtp = newDeviceOtp,
captchaToken = captchaToken ?: twoFactorResponse?.captchaToken,
deviceData = twoFactorDeviceData,
orgIdentifier = orgIdentifier,
)
}
?: LoginResult.Error(errorMessage = null)
override suspend fun login(
email: String,
ssoCode: String,
@@ -755,6 +791,16 @@ class AuthRepositoryImpl(
}
?: ResendEmailResult.Error(message = null)
override suspend fun resendNewDeviceOtp(): ResendEmailResult =
resendNewDeviceOtpRequestJson
?.let { jsonRequest ->
accountsService.resendNewDeviceOtp(body = jsonRequest).fold(
onFailure = { ResendEmailResult.Error(message = it.message) },
onSuccess = { ResendEmailResult.Success },
)
}
?: ResendEmailResult.Error(message = null)
override fun switchAccount(userId: String): SwitchAccountResult {
val currentUserState = authDiskSource.userState ?: return SwitchAccountResult.NoChange
val previousActiveUserId = currentUserState.activeUserId
@@ -864,14 +910,7 @@ class AuthRepositoryImpl(
}
is RegisterResponseJson.Invalid -> {
RegisterResult.Error(
errorMessage = it
.validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
?: it.message,
)
RegisterResult.Error(errorMessage = it.message)
}
}
},
@@ -1079,6 +1118,7 @@ class AuthRepositoryImpl(
}
is VaultUnlockResult.AuthenticationError,
VaultUnlockResult.BiometricDecodingError,
VaultUnlockResult.InvalidStateError,
VaultUnlockResult.GenericError,
-> {
@@ -1153,13 +1193,21 @@ class AuthRepositoryImpl(
)
.fold(
onSuccess = {
if (it.token.isNullOrBlank()) {
PrevalidateSsoResult.Failure
} else {
PrevalidateSsoResult.Success(it.token)
when (it) {
is PrevalidateSsoResponseJson.Error -> {
PrevalidateSsoResult.Failure(message = it.message)
}
is PrevalidateSsoResponseJson.Success -> {
if (it.token.isNullOrBlank()) {
PrevalidateSsoResult.Failure()
} else {
PrevalidateSsoResult.Success(token = it.token)
}
}
}
},
onFailure = { PrevalidateSsoResult.Failure },
onFailure = { PrevalidateSsoResult.Failure() },
)
override fun setSsoCallbackResult(result: SsoCallbackResult) {
@@ -1186,12 +1234,17 @@ class AuthRepositoryImpl(
)
override suspend fun getPasswordStrength(
email: String,
email: String?,
password: String,
): PasswordStrengthResult =
authSdkSource
.passwordStrength(
email = email,
email = email
?: userStateFlow
.value
?.activeAccount
?.email
.orEmpty(),
password = password,
)
.fold(
@@ -1249,41 +1302,17 @@ class AuthRepositoryImpl(
?.activeAccount
?.profile
?: return ValidatePinResult.Error
val privateKey = authDiskSource
.getPrivateKey(userId = activeAccount.userId)
?: return ValidatePinResult.Error
val pinProtectedUserKey = authDiskSource
.getPinProtectedUserKey(userId = activeAccount.userId)
?: return ValidatePinResult.Error
// HACK: As the SDK doesn't provide a way to directly validate the pin yet, we instead
// try to initialize the user crypto, and if it succeeds then the PIN is correct, otherwise
// the PIN is incorrect.
return vaultSdkSource
.initializeCrypto(
.validatePin(
userId = activeAccount.userId,
request = InitUserCryptoRequest(
kdfParams = activeAccount.toSdkParams(),
email = activeAccount.email,
privateKey = privateKey,
method = InitUserCryptoMethod.Pin(
pin = pin,
pinProtectedUserKey = pinProtectedUserKey,
),
),
pin = pin,
pinProtectedUserKey = pinProtectedUserKey,
)
.fold(
onSuccess = {
when (it) {
InitializeCryptoResult.Success -> {
ValidatePinResult.Success(isValid = true)
}
is InitializeCryptoResult.AuthenticationError -> {
ValidatePinResult.Success(isValid = false)
}
}
},
onSuccess = { ValidatePinResult.Success(isValid = it) },
onFailure = { ValidatePinResult.Error },
)
}
@@ -1308,7 +1337,15 @@ class AuthRepositoryImpl(
)
.fold(
onSuccess = {
SendVerificationEmailResult.Success(it)
when (it) {
is SendVerificationEmailResponseJson.Invalid -> {
SendVerificationEmailResult.Error(it.message)
}
is SendVerificationEmailResponseJson.Success -> {
SendVerificationEmailResult.Success(it.emailVerificationToken)
}
}
},
onFailure = {
SendVerificationEmailResult.Error(null)
@@ -1340,8 +1377,99 @@ class AuthRepositoryImpl(
)
}
override fun setOnboardingStatus(userId: String, status: OnboardingStatus?) {
authDiskSource.storeOnboardingStatus(userId = userId, onboardingStatus = status)
override fun setOnboardingStatus(status: OnboardingStatus) {
activeUserId?.let { userId ->
authDiskSource.storeOnboardingStatus(
userId = userId,
onboardingStatus = status,
)
}
}
override fun getNewDeviceNoticeState(): NewDeviceNoticeState? {
return activeUserId?.let { userId ->
authDiskSource.getNewDeviceNoticeState(userId = userId)
}
}
override fun setNewDeviceNoticeState(newState: NewDeviceNoticeState?) {
activeUserId?.let { userId ->
authDiskSource.storeNewDeviceNoticeState(userId = userId, newState = newState)
}
}
override fun checkUserNeedsNewDeviceTwoFactorNotice(): Boolean {
return activeUserId?.let { userId ->
val temporaryFlag = featureFlagManager.getFeatureFlag(FlagKey.NewDeviceTemporaryDismiss)
val permanentFlag = featureFlagManager.getFeatureFlag(FlagKey.NewDevicePermanentDismiss)
// check if feature flags are disabled
if (!temporaryFlag && !permanentFlag) {
return false
}
if (!newDeviceNoticePreConditionsValid()) {
return false
}
val newDeviceNoticeState = authDiskSource.getNewDeviceNoticeState(userId = userId)
return when (newDeviceNoticeState.displayStatus) {
// if the user has already attested email access but permanent flag is enabled,
// the notice needs to appear again
NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL -> permanentFlag
// if the user has already seen but 7 days have already passed,
// the notice needs to appear again
NewDeviceNoticeDisplayStatus.HAS_SEEN ->
newDeviceNoticeState.shouldDisplayNoticeIfSeen
NewDeviceNoticeDisplayStatus.HAS_NOT_SEEN -> true
// the user never needs to see the notice again
NewDeviceNoticeDisplayStatus.CAN_ACCESS_EMAIL_PERMANENT -> false
}
}
?: false
}
/**
* Checks if the preconditions are met for a user to see a new device notice:
* - Must be a Bitwarden cloud user.
* - The account must be at least one week old.
* - Cannot have an active policy requiring SSO to be enabled.
* - Cannot have two-factor authentication enabled.
*/
private fun newDeviceNoticePreConditionsValid(): Boolean {
val checkEnvironment = !featureFlagManager.getFeatureFlag(FlagKey.IgnoreEnvironmentCheck)
val isSelfHosted = environmentRepository.environment.type == Environment.Type.SELF_HOSTED
if (checkEnvironment && isSelfHosted) {
return false
}
val userProfile = authDiskSource.userState?.activeAccount?.profile
val isProfileAtLeastWeekOld = userProfile
?.let {
it.creationDate
?.plusWeeks(1)
?.isBefore(
ZonedDateTime.now(),
)
}
?: false
if (!isProfileAtLeastWeekOld) {
return false
}
val hasTwoFactorEnabled = userProfile
?.isTwoFactorEnabled
?: false
if (hasTwoFactorEnabled) {
return false
}
val hasSSOPolicy =
policyManager.getActivePolicies(type = PolicyTypeJson.REQUIRE_SSO)
.any { p -> p.isEnabled }
return !hasSSOPolicy
}
@Suppress("CyclomaticComplexMethod")
@@ -1499,6 +1627,7 @@ class AuthRepositoryImpl(
deviceData: DeviceDataModel? = null,
orgIdentifier: String? = null,
captchaToken: String?,
newDeviceOtp: String? = null,
): LoginResult = identityService
.getToken(
uniqueAppId = authDiskSource.uniqueAppId,
@@ -1506,11 +1635,16 @@ class AuthRepositoryImpl(
authModel = authModel,
twoFactorData = twoFactorData ?: getRememberedTwoFactorData(email),
captchaToken = captchaToken,
newDeviceOtp = newDeviceOtp,
)
.fold(
onFailure = {
when (configDiskSource.serverConfig?.isOfficialBitwardenServer) {
false -> LoginResult.UnofficialServerError
onFailure = { throwable ->
when {
throwable.isSslHandShakeError() -> LoginResult.CertificateError
configDiskSource.serverConfig?.isOfficialBitwardenServer == false -> {
LoginResult.UnofficialServerError
}
else -> LoginResult.Error(errorMessage = null)
}
},
@@ -1535,9 +1669,22 @@ class AuthRepositoryImpl(
orgIdentifier = orgIdentifier,
)
is GetTokenResponseJson.Invalid -> LoginResult.Error(
errorMessage = loginResponse.errorMessage,
)
is GetTokenResponseJson.Invalid -> {
when (loginResponse.invalidType) {
is GetTokenResponseJson.Invalid.InvalidType.NewDeviceVerification ->
handleLoginCommonNewDeviceVerification(
email = email,
authModel = authModel,
error = loginResponse.errorMessage,
)
is GetTokenResponseJson.Invalid.InvalidType.GenericInvalid -> {
LoginResult.Error(
errorMessage = loginResponse.errorMessage,
)
}
}
}
}
},
)
@@ -1625,15 +1772,6 @@ class AuthRepositoryImpl(
)
settingsRepository.hasUserLoggedInOrCreatedAccount = true
val shouldSetOnboardingStatus = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow) &&
!settingsRepository.getUserHasLoggedInValue(userId = userId)
if (shouldSetOnboardingStatus) {
setOnboardingStatus(
userId = userId,
status = OnboardingStatus.NOT_STARTED,
)
}
authDiskSource.userState = userStateJson
loginResponse.key?.let {
// Only set the value if it's present, since we may have set it already
@@ -1662,6 +1800,7 @@ class AuthRepositoryImpl(
twoFactorResponse = null
resendEmailRequestJson = null
twoFactorDeviceData = null
resendNewDeviceOtpRequestJson = null
settingsRepository.setDefaultsIfNecessary(userId = userId)
settingsRepository.storeUserHasLoggedInValue(userId)
vaultRepository.syncIfNecessary()
@@ -1694,6 +1833,24 @@ class AuthRepositoryImpl(
return LoginResult.TwoFactorRequired
}
/**
* A helper method that processes the
* [GetTokenResponseJson.Invalid.InvalidType.NewDeviceVerification] when logging in.
*/
private fun handleLoginCommonNewDeviceVerification(
email: String,
authModel: IdentityTokenAuthModel,
error: String?,
): LoginResult {
identityTokenAuthModel = authModel
resendNewDeviceOtpRequestJson = ResendNewDeviceOtpRequestJson(
email = email,
passwordHash = authModel.password,
)
return LoginResult.NewDeviceVerification(error)
}
/**
* Attempt to unlock the current user's vault with key connector data.
*/

View File

@@ -28,4 +28,14 @@ sealed class LoginResult {
* There was an error while logging into an unofficial Bitwarden server.
*/
data object UnofficialServerError : LoginResult()
/**
* There was an error in validating the certificate chain for the server
*/
data object CertificateError : LoginResult()
/**
* New device verification is required
*/
data class NewDeviceVerification(val errorMessage: String?) : LoginResult()
}

View File

@@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
*/
fun VaultUnlockError.toLoginErrorResult(): LoginResult.Error = when (this) {
is VaultUnlockResult.AuthenticationError -> LoginResult.Error(this.message)
VaultUnlockResult.BiometricDecodingError,
VaultUnlockResult.GenericError,
VaultUnlockResult.InvalidStateError,
-> LoginResult.Error(errorMessage = null)

View File

@@ -14,5 +14,7 @@ sealed class PrevalidateSsoResult {
/**
* There was an error in prevalidation.
*/
data object Failure : PrevalidateSsoResult()
data class Failure(
val message: String? = null,
) : PrevalidateSsoResult()
}

View File

@@ -25,6 +25,7 @@ fun GetTokenResponseJson.Success.toUserState(
userId = userId,
email = jwtTokenData.email,
isEmailVerified = jwtTokenData.isEmailVerified,
isTwoFactorEnabled = null,
name = jwtTokenData.name,
stamp = null,
organizationId = null,
@@ -36,6 +37,7 @@ fun GetTokenResponseJson.Success.toUserState(
kdfMemory = this.kdfMemory,
kdfParallelism = this.kdfParallelism,
userDecryptionOptions = this.userDecryptionOptions,
creationDate = null,
),
settings = AccountJson.Settings(
environmentUrlData = environmentUrlData,

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.repository.util
import com.x8bit.bitwarden.data.auth.repository.model.JwtTokenDataJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
import kotlinx.serialization.json.Json
import timber.log.Timber
/**
* Internal, generally basic [Json] instance for JWT parsing purposes.
@@ -17,17 +18,24 @@ private val json: Json by lazy {
/**
* Parses a [JwtTokenDataJson] from the given [jwtToken], or `null` if this parsing is not possible.
*/
@Suppress("MagicNumber")
@Suppress("MagicNumber", "TooGenericExceptionCaught")
fun parseJwtTokenDataOrNull(jwtToken: String): JwtTokenDataJson? {
val parts = jwtToken.split(".")
if (parts.size != 3) return null
if (parts.size != 3) {
Timber.e(IllegalArgumentException("Incorrect number of parts"), "Invalid JWT Token")
return null
}
val dataJson = parts[1]
val decodedDataJson = dataJson.base64UrlDecodeOrNull() ?: return null
val decodedDataJson = dataJson.base64UrlDecodeOrNull() ?: run {
Timber.e(IllegalArgumentException("Unable to decode"), "Invalid JWT Token")
return null
}
return try {
json.decodeFromString<JwtTokenDataJson>(decodedDataJson)
} catch (_: Throwable) {
} catch (throwable: Throwable) {
Timber.e(throwable, "Failed to decode JwtTokenDataJson")
null
}
}

View File

@@ -59,6 +59,8 @@ fun UserStateJson.toUpdatedUserStateJson(
avatarColorHex = syncProfile.avatarColor,
stamp = syncProfile.securityStamp,
hasPremium = syncProfile.isPremium || syncProfile.isPremiumFromOrganization,
isTwoFactorEnabled = syncProfile.isTwoFactorEnabled,
creationDate = syncProfile.creationDate,
)
val updatedAccount = account.copy(profile = updatedProfile)
return this

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.autofill.accessibility.di
import android.content.Context
import android.content.pm.PackageManager
import android.os.PowerManager
import android.view.accessibility.AccessibilityManager
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
@@ -55,8 +56,12 @@ object AccessibilityModule {
@Singleton
@Provides
fun providesAccessibilityEnabledManager(): AccessibilityEnabledManager =
AccessibilityEnabledManagerImpl()
fun providesAccessibilityEnabledManager(
accessibilityManager: AccessibilityManager,
): AccessibilityEnabledManager =
AccessibilityEnabledManagerImpl(
accessibilityManager = accessibilityManager,
)
@Singleton
@Provides
@@ -110,6 +115,12 @@ object AccessibilityModule {
@ApplicationContext context: Context,
): PackageManager = context.packageManager
@Singleton
@Provides
fun provideAccessibilityManager(
@ApplicationContext context: Context,
): AccessibilityManager = context.getSystemService(AccessibilityManager::class.java)
@Singleton
@Provides
fun providesPowerManager(

View File

@@ -1,36 +0,0 @@
package com.x8bit.bitwarden.data.autofill.accessibility.di
import android.content.Context
import androidx.lifecycle.LifecycleCoroutineScope
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManagerImpl
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityScoped
/**
* Provides dependencies within the accessibility package scoped to the activity.
*/
@Module
@InstallIn(ActivityComponent::class)
object ActivityAccessibilityModule {
@ActivityScoped
@Provides
fun providesAccessibilityActivityManager(
@ApplicationContext context: Context,
accessibilityEnabledManager: AccessibilityEnabledManager,
appStateManager: AppStateManager,
lifecycleScope: LifecycleCoroutineScope,
): AccessibilityActivityManager =
AccessibilityActivityManagerImpl(
context = context,
accessibilityEnabledManager = accessibilityEnabledManager,
appStateManager = appStateManager,
lifecycleScope = lifecycleScope,
)
}

View File

@@ -1,10 +0,0 @@
package com.x8bit.bitwarden.data.autofill.accessibility.manager
import android.app.Activity
/**
* A helper for dealing with accessibility configuration that must be scoped to a specific
* [Activity]. In particular, this should be injected into an [Activity] to ensure that the
* [AccessibilityEnabledManager] reports correct values.
*/
interface AccessibilityActivityManager

View File

@@ -1,28 +0,0 @@
package com.x8bit.bitwarden.data.autofill.accessibility.manager
import android.content.Context
import androidx.lifecycle.LifecycleCoroutineScope
import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
/**
* The default implementation of the [AccessibilityActivityManager].
*/
class AccessibilityActivityManagerImpl(
private val context: Context,
private val accessibilityEnabledManager: AccessibilityEnabledManager,
appStateManager: AppStateManager,
lifecycleScope: LifecycleCoroutineScope,
) : AccessibilityActivityManager {
init {
appStateManager
.appForegroundStateFlow
.onEach {
accessibilityEnabledManager.isAccessibilityEnabled =
context.isAccessibilityServiceEnabled
}
.launchIn(lifecycleScope)
}
}

View File

@@ -7,15 +7,7 @@ import kotlinx.coroutines.flow.StateFlow
*/
interface AccessibilityEnabledManager {
/**
* Whether or not the accessibility service should be considered enabled.
*
* Note that changing this does not enable or disable autofill; it is only an indicator that
* this has occurred elsewhere.
*/
var isAccessibilityEnabled: Boolean
/**
* Emits updates that track [isAccessibilityEnabled] values.
* Emits updates that track whether the accessibility autofill service is enabled..
*/
val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
}

View File

@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.autofill.accessibility.manager
import android.view.accessibility.AccessibilityManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -7,14 +8,20 @@ import kotlinx.coroutines.flow.asStateFlow
/**
* The default implementation of [AccessibilityEnabledManager].
*/
class AccessibilityEnabledManagerImpl : AccessibilityEnabledManager {
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(value = false)
class AccessibilityEnabledManagerImpl(
accessibilityManager: AccessibilityManager,
) : AccessibilityEnabledManager {
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(
value = accessibilityManager.isEnabled,
)
override var isAccessibilityEnabled: Boolean
get() = mutableIsAccessibilityEnabledStateFlow.value
set(value) {
mutableIsAccessibilityEnabledStateFlow.value = value
}
init {
accessibilityManager.addAccessibilityStateChangeListener(
AccessibilityManager.AccessibilityStateChangeListener { isEnabled ->
mutableIsAccessibilityEnabledStateFlow.value = isEnabled
},
)
}
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()

View File

@@ -9,5 +9,5 @@ data class FillableFields(
val usernameField: AccessibilityNodeInfo?,
val passwordFields: List<AccessibilityNodeInfo>,
) {
val hasFields: Boolean = usernameField != null && passwordFields.isNotEmpty()
val hasFields: Boolean = usernameField != null || passwordFields.isNotEmpty()
}

View File

@@ -128,6 +128,11 @@ private val ACCESSIBILITY_SUPPORTED_BROWSERS = listOf(
// 2nd = Anticipation
possibleUrlFieldIds = listOf("url_bar_title", "mozac_browser_toolbar_url_view"),
),
Browser(
packageName = "org.ironfoxoss.ironfox",
// 2nd = Legacy
possibleUrlFieldIds = listOf("mozac_browser_toolbar_url_view", "url_bar_title"),
),
Browser(packageName = "org.mozilla.fenix", urlFieldId = "mozac_browser_toolbar_url_view"),
// [DEPRECATED ENTRY]
Browser(
@@ -191,11 +196,6 @@ private val ACCESSIBILITY_SUPPORTED_BROWSERS = listOf(
),
Browser(packageName = "org.ungoogled.chromium.extensions.stable", urlFieldId = "url_bar"),
Browser(packageName = "org.ungoogled.chromium.stable", urlFieldId = "url_bar"),
Browser(
packageName = "us.spotco.fennec_dos",
// 2nd = Legacy
possibleUrlFieldIds = listOf("mozac_browser_toolbar_url_view", "url_bar_title"),
),
// [Section B] Entries only present here
// TODO: Test the compatibility of these with Autofill Framework

View File

@@ -8,6 +8,9 @@ import androidx.lifecycle.lifecycleScope
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManager
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManagerImpl
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import dagger.Module
import dagger.Provides
@@ -23,19 +26,32 @@ import dagger.hilt.android.scopes.ActivityScoped
@InstallIn(ActivityComponent::class)
object ActivityAutofillModule {
@ActivityScoped
@ActivityScopedManager
@Provides
fun provideActivityScopedChromeThirdPartyAutofillManager(
activity: Activity,
): ChromeThirdPartyAutofillManager = ChromeThirdPartyAutofillManagerImpl(
context = activity.baseContext,
)
@ActivityScoped
@Provides
fun provideAutofillActivityManager(
@ActivityScopedManager autofillManager: AutofillManager,
@ActivityScopedManager chromeThirdPartyAutofillManager: ChromeThirdPartyAutofillManager,
appStateManager: AppStateManager,
autofillEnabledManager: AutofillEnabledManager,
lifecycleScope: LifecycleCoroutineScope,
chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager,
): AutofillActivityManager =
AutofillActivityManagerImpl(
autofillManager = autofillManager,
chromeThirdPartyAutofillManager = chromeThirdPartyAutofillManager,
appStateManager = appStateManager,
autofillEnabledManager = autofillEnabledManager,
lifecycleScope = lifecycleScope,
chromeThirdPartyAutofillEnabledManager = chromeThirdPartyAutofillEnabledManager,
)
/**

View File

@@ -15,12 +15,15 @@ import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManagerImpl
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
import com.x8bit.bitwarden.data.autofill.parser.AutofillParserImpl
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessor
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessorImpl
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProviderImpl
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
@@ -54,6 +57,15 @@ object AutofillModule {
fun providesAutofillEnabledManager(): AutofillEnabledManager =
AutofillEnabledManagerImpl()
@Singleton
@Provides
fun providesChromeAutofillEnabledManager(
featureFlagManager: FeatureFlagManager,
): ChromeThirdPartyAutofillEnabledManager =
ChromeThirdPartyAutofillEnabledManagerImpl(
featureFlagManager = featureFlagManager,
)
@Singleton
@Provides
fun provideAutofillCompletionManager(

View File

@@ -8,9 +8,13 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManagerImpl
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManager
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManagerImpl
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessor
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessorImpl
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
@@ -42,6 +46,8 @@ object Fido2ProviderModule {
fido2CredentialManager: Fido2CredentialManager,
dispatcherManager: DispatcherManager,
intentManager: IntentManager,
biometricsEncryptionManager: BiometricsEncryptionManager,
featureFlagManager: FeatureFlagManager,
clock: Clock,
): Fido2ProviderProcessor =
Fido2ProviderProcessorImpl(
@@ -52,23 +58,34 @@ object Fido2ProviderModule {
fido2CredentialManager,
intentManager,
clock,
biometricsEncryptionManager,
featureFlagManager,
dispatcherManager,
)
@Provides
@Singleton
fun provideFido2CredentialManager(
assetManager: AssetManager,
digitalAssetLinkService: DigitalAssetLinkService,
vaultSdkSource: VaultSdkSource,
fido2CredentialStore: Fido2CredentialStore,
fido2OriginManager: Fido2OriginManager,
json: Json,
): Fido2CredentialManager =
Fido2CredentialManagerImpl(
assetManager = assetManager,
digitalAssetLinkService = digitalAssetLinkService,
vaultSdkSource = vaultSdkSource,
fido2CredentialStore = fido2CredentialStore,
fido2OriginManager = fido2OriginManager,
json = json,
)
@Provides
@Singleton
fun provideFido2OriginManager(
assetManager: AssetManager,
digitalAssetLinkService: DigitalAssetLinkService,
): Fido2OriginManager =
Fido2OriginManagerImpl(
assetManager = assetManager,
digitalAssetLinkService = digitalAssetLinkService,
)
}

View File

@@ -1,12 +1,10 @@
package com.x8bit.bitwarden.data.autofill.fido2.manager
import androidx.credentials.provider.CallingAppInfo
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
@@ -26,14 +24,6 @@ interface Fido2CredentialManager {
*/
var authenticationAttempts: Int
/**
* Attempt to validate the RP and origin of the provided [callingAppInfo] and [relyingPartyId].
*/
suspend fun validateOrigin(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult
/**
* Attempt to extract FIDO 2 passkey attestation options from the system [requestJson], or null.
*/
@@ -53,7 +43,7 @@ interface Fido2CredentialManager {
*/
suspend fun registerFido2Credential(
userId: String,
fido2CredentialRequest: Fido2CredentialRequest,
fido2CreateCredentialRequest: Fido2CreateCredentialRequest,
selectedCipherView: CipherView,
): Fido2RegisterCredentialResult

View File

@@ -6,43 +6,37 @@ import com.bitwarden.fido.Origin
import com.bitwarden.fido.UnverifiedAssetLink
import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import com.x8bit.bitwarden.data.platform.util.getAppOrigin
import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint
import com.x8bit.bitwarden.data.platform.util.getSignatureFingerprintAsHexString
import com.x8bit.bitwarden.data.platform.util.validatePrivilegedApp
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2CredentialRequest
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidAttestationResponse
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential
import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.prefixHttpsIfNecessaryOrNull
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
private const val GOOGLE_ALLOW_LIST_FILE_NAME = "fido2_privileged_google.json"
private const val COMMUNITY_ALLOW_LIST_FILE_NAME = "fido2_privileged_community.json"
import timber.log.Timber
/**
* Primary implementation of [Fido2CredentialManager].
*/
@Suppress("TooManyFunctions")
class Fido2CredentialManagerImpl(
private val assetManager: AssetManager,
private val digitalAssetLinkService: DigitalAssetLinkService,
private val vaultSdkSource: VaultSdkSource,
private val fido2CredentialStore: Fido2CredentialStore,
private val fido2OriginManager: Fido2OriginManager,
private val json: Json,
) : Fido2CredentialManager,
Fido2CredentialStore by fido2CredentialStore {
@@ -53,45 +47,49 @@ class Fido2CredentialManagerImpl(
override suspend fun registerFido2Credential(
userId: String,
fido2CredentialRequest: Fido2CredentialRequest,
fido2CreateCredentialRequest: Fido2CreateCredentialRequest,
selectedCipherView: CipherView,
): Fido2RegisterCredentialResult {
val clientData = if (fido2CredentialRequest.callingAppInfo.isOriginPopulated()) {
fido2CredentialRequest
.callingAppInfo
val callingAppInfo = fido2CreateCredentialRequest.callingAppInfo
val clientData = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
ClientData.DefaultWithExtraData(androidPackageName = callingAppInfo.packageName)
} else {
callingAppInfo
.getAppSigningSignatureFingerprint()
?.let { ClientData.DefaultWithCustomHash(hash = it) }
?: return Fido2RegisterCredentialResult.Error
} else {
ClientData.DefaultWithExtraData(
androidPackageName = fido2CredentialRequest
.callingAppInfo
.packageName,
)
?: return Fido2RegisterCredentialResult.Error(
R.string.passkey_operation_failed_because_app_is_signed_incorrectly.asText(),
)
}
val sdkOrigin = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
val host = getOriginUrlFromAttestationOptionsOrNull(
requestJson = fido2CreateCredentialRequest.requestJson,
)
?: return Fido2RegisterCredentialResult.Error(
R.string.passkey_operation_failed_because_host_url_is_not_present_in_request
.asText(),
)
Origin.Android(
UnverifiedAssetLink(
packageName = callingAppInfo.packageName,
sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
?: return Fido2RegisterCredentialResult.Error(
R.string.passkey_operation_failed_because_app_signature_is_invalid
.asText(),
),
host = host,
assetLinkUrl = host,
),
)
} else {
Origin.Web(fido2CreateCredentialRequest.origin)
}
val assetLinkUrl = fido2CredentialRequest
.origin
?: getOriginUrlFromAttestationOptionsOrNull(fido2CredentialRequest.requestJson)
?: return Fido2RegisterCredentialResult.Error
val origin = Origin.Android(
UnverifiedAssetLink(
packageName = fido2CredentialRequest.packageName,
sha256CertFingerprint = fido2CredentialRequest
.callingAppInfo
.getSignatureFingerprintAsHexString()
?: return Fido2RegisterCredentialResult.Error,
host = assetLinkUrl.toHostOrPathOrNull()
?: return Fido2RegisterCredentialResult.Error,
assetLinkUrl = assetLinkUrl,
),
)
return vaultSdkSource
.registerFido2Credential(
request = RegisterFido2CredentialRequest(
userId = userId,
origin = origin,
requestJson = """{"publicKey": ${fido2CredentialRequest.requestJson}}""",
origin = sdkOrigin,
requestJson = """{"publicKey": ${fido2CreateCredentialRequest.requestJson}}""",
clientData = clientData,
selectedCipherView = selectedCipherView,
// User verification is handled prior to engaging the SDK. We always respond
@@ -104,20 +102,22 @@ class Fido2CredentialManagerImpl(
.mapCatching { json.encodeToString(it) }
.fold(
onSuccess = { Fido2RegisterCredentialResult.Success(it) },
onFailure = { Fido2RegisterCredentialResult.Error },
onFailure = {
Fido2RegisterCredentialResult.Error(
R.string.passkey_registration_failed_due_to_an_internal_error.asText(),
)
},
)
}
override suspend fun validateOrigin(
private suspend fun validateOrigin(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult {
return if (callingAppInfo.isOriginPopulated()) {
validatePrivilegedAppOrigin(callingAppInfo)
} else {
validateCallingApplicationAssetLinks(callingAppInfo, relyingPartyId)
}
}
): Fido2ValidateOriginResult = fido2OriginManager
.validateOrigin(
callingAppInfo = callingAppInfo,
relyingPartyId = relyingPartyId,
)
override fun getPasskeyAttestationOptionsOrNull(
requestJson: String,
@@ -125,8 +125,10 @@ class Fido2CredentialManagerImpl(
try {
json.decodeFromString<PasskeyAttestationOptions>(requestJson)
} catch (e: SerializationException) {
Timber.e(e, "Failed to decode passkey attestation options.")
null
} catch (e: IllegalArgumentException) {
Timber.e(e, "Failed to decode passkey attestation options.")
null
}
@@ -136,11 +138,14 @@ class Fido2CredentialManagerImpl(
try {
json.decodeFromString<PasskeyAssertionOptions>(requestJson)
} catch (e: SerializationException) {
Timber.e(e, "Failed to decode passkey assertion options: $e")
null
} catch (e: IllegalArgumentException) {
Timber.e(e, "Failed to decode passkey assertion options: $e")
null
}
@Suppress("LongMethod")
override suspend fun authenticateFido2Credential(
userId: String,
request: Fido2CredentialAssertionRequest,
@@ -150,39 +155,52 @@ class Fido2CredentialManagerImpl(
val clientData = request.clientDataHash
?.let { ClientData.DefaultWithCustomHash(hash = it) }
?: ClientData.DefaultWithExtraData(androidPackageName = callingAppInfo.getAppOrigin())
val origin = callingAppInfo.origin
?: getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
?: return Fido2CredentialAssertionResult.Error
val relyingPartyId = json
.decodeFromStringOrNull<PasskeyAssertionOptions>(request.requestJson)
?.relyingPartyId
?: return Fido2CredentialAssertionResult.Error
?: return Fido2CredentialAssertionResult.Error(
R.string.passkey_operation_failed_because_relying_party_cannot_be_identified
.asText(),
)
val validateOriginResult = validateOrigin(
callingAppInfo = callingAppInfo,
relyingPartyId = relyingPartyId,
)
val sdkOrigin = if (!request.origin.isNullOrEmpty()) {
Origin.Web(request.origin)
} else {
val hostUrl = getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
?: return Fido2CredentialAssertionResult.Error(
R.string.passkey_operation_failed_because_host_url_is_not_present_in_request
.asText(),
)
Origin.Android(
UnverifiedAssetLink(
packageName = callingAppInfo.packageName,
sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
?: return Fido2CredentialAssertionResult.Error(
R.string.passkey_operation_failed_because_app_signature_is_invalid
.asText(),
),
host = hostUrl,
assetLinkUrl = hostUrl,
),
)
}
return when (validateOriginResult) {
is Fido2ValidateOriginResult.Error -> {
Fido2CredentialAssertionResult.Error
Fido2CredentialAssertionResult.Error(validateOriginResult.messageResId.asText())
}
Fido2ValidateOriginResult.Success -> {
is Fido2ValidateOriginResult.Success -> {
vaultSdkSource
.authenticateFido2Credential(
request = AuthenticateFido2CredentialRequest(
userId = userId,
origin = Origin.Android(
UnverifiedAssetLink(
callingAppInfo.packageName,
callingAppInfo.getSignatureFingerprintAsHexString()
?: return Fido2CredentialAssertionResult.Error,
origin.toHostOrPathOrNull()
?: return Fido2CredentialAssertionResult.Error,
origin,
),
),
origin = sdkOrigin,
requestJson = """{"publicKey": ${request.requestJson}}""",
clientData = clientData,
selectedCipherView = selectedCipherView,
@@ -194,146 +212,31 @@ class Fido2CredentialManagerImpl(
.mapCatching { json.encodeToString(it) }
.fold(
onSuccess = { Fido2CredentialAssertionResult.Success(it) },
onFailure = { Fido2CredentialAssertionResult.Error },
)
}
}
}
private suspend fun validateCallingApplicationAssetLinks(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult {
return digitalAssetLinkService
.getDigitalAssetLinkForRp(relyingParty = relyingPartyId)
.onFailure {
return Fido2ValidateOriginResult.Error.AssetLinkNotFound
}
.map { statements ->
statements
.filterMatchingAppStatementsOrNull(
rpPackageName = callingAppInfo.packageName,
)
?: return Fido2ValidateOriginResult.Error.ApplicationNotFound
}
.map { matchingStatements ->
callingAppInfo
.getSignatureFingerprintAsHexString()
?.let { certificateFingerprint ->
matchingStatements
.filterMatchingAppSignaturesOrNull(
signature = certificateFingerprint,
onFailure = {
Timber.e(it, "Failed to authenticate FIDO2 credential.")
Fido2CredentialAssertionResult.Error(
R.string.passkey_authentication_failed_due_to_an_internal_error
.asText(),
)
}
?: return Fido2ValidateOriginResult.Error.ApplicationNotVerified
}
.fold(
onSuccess = {
Fido2ValidateOriginResult.Success
},
onFailure = {
Fido2ValidateOriginResult.Error.Unknown
},
)
}
private suspend fun validatePrivilegedAppOrigin(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult {
val googleAllowListResult =
validatePrivilegedAppSignatureWithGoogleList(callingAppInfo)
return when (googleAllowListResult) {
is Fido2ValidateOriginResult.Success -> {
// Application was found and successfully validated against the Google allow list so
// we can return the result as the final validation result.
googleAllowListResult
}
is Fido2ValidateOriginResult.Error -> {
// Check the community allow list if the Google allow list failed, and return the
// result as the final validation result.
validatePrivilegedAppSignatureWithCommunityList(callingAppInfo)
},
)
}
}
}
private suspend fun validatePrivilegedAppSignatureWithGoogleList(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult =
validatePrivilegedAppSignatureWithAllowList(
callingAppInfo = callingAppInfo,
fileName = GOOGLE_ALLOW_LIST_FILE_NAME,
)
private suspend fun validatePrivilegedAppSignatureWithCommunityList(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult =
validatePrivilegedAppSignatureWithAllowList(
callingAppInfo = callingAppInfo,
fileName = COMMUNITY_ALLOW_LIST_FILE_NAME,
)
private suspend fun validatePrivilegedAppSignatureWithAllowList(
callingAppInfo: CallingAppInfo,
fileName: String,
): Fido2ValidateOriginResult =
assetManager
.readAsset(fileName)
.map { allowList ->
callingAppInfo.validatePrivilegedApp(
allowList = allowList,
)
}
.fold(
onSuccess = { it },
onFailure = { Fido2ValidateOriginResult.Error.Unknown },
)
/**
* Returns statements targeting the calling Android application, or null.
*/
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppStatementsOrNull(
rpPackageName: String,
): List<DigitalAssetLinkResponseJson>? =
filter { statement ->
val target = statement.target
target.namespace == "android_app" &&
target.packageName == rpPackageName &&
statement.relation.containsAll(
listOf(
"delegate_permission/common.get_login_creds",
"delegate_permission/common.handle_all_urls",
),
)
}
.takeUnless { it.isEmpty() }
/**
* Returns statements that match the given [signature], or null.
*/
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppSignaturesOrNull(
signature: String,
): List<DigitalAssetLinkResponseJson>? =
filter { statement ->
statement.target.sha256CertFingerprints
?.contains(signature)
?: false
}
.takeUnless { it.isEmpty() }
override fun hasAuthenticationAttemptsRemaining(): Boolean =
authenticationAttempts < MAX_AUTHENTICATION_ATTEMPTS
private fun getOriginUrlFromAssertionOptionsOrNull(requestJson: String) =
getPasskeyAssertionOptionsOrNull(requestJson)
?.relyingPartyId
?.let { "$HTTPS$it" }
?.prefixHttpsIfNecessaryOrNull()
private fun getOriginUrlFromAttestationOptionsOrNull(requestJson: String) =
getPasskeyAttestationOptionsOrNull(requestJson)
?.relyingParty
?.id
?.let { "$HTTPS$it" }
?.prefixHttpsIfNecessaryOrNull()
}
private const val MAX_AUTHENTICATION_ATTEMPTS = 5

View File

@@ -0,0 +1,23 @@
package com.x8bit.bitwarden.data.autofill.fido2.manager
import androidx.credentials.provider.CallingAppInfo
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
/**
* Responsible for managing FIDO2 origin validation.
*/
interface Fido2OriginManager {
/**
* Validates the origin of a calling app.
*
* @param callingAppInfo The calling app info.
* @param relyingPartyId The relying party ID.
*
* @return The result of the validation.
*/
suspend fun validateOrigin(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult
}

View File

@@ -0,0 +1,156 @@
package com.x8bit.bitwarden.data.autofill.fido2.manager
import androidx.credentials.provider.CallingAppInfo
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.util.getSignatureFingerprintAsHexString
import com.x8bit.bitwarden.data.platform.util.validatePrivilegedApp
import timber.log.Timber
private const val GOOGLE_ALLOW_LIST_FILE_NAME = "fido2_privileged_google.json"
private const val COMMUNITY_ALLOW_LIST_FILE_NAME = "fido2_privileged_community.json"
/**
* Primary implementation of [Fido2OriginManager].
*/
@Suppress("TooManyFunctions")
class Fido2OriginManagerImpl(
private val assetManager: AssetManager,
private val digitalAssetLinkService: DigitalAssetLinkService,
) : Fido2OriginManager {
override suspend fun validateOrigin(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult {
return if (callingAppInfo.isOriginPopulated()) {
validatePrivilegedAppOrigin(callingAppInfo)
} else {
validateCallingApplicationAssetLinks(callingAppInfo, relyingPartyId)
}
}
private suspend fun validateCallingApplicationAssetLinks(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult = digitalAssetLinkService
.getDigitalAssetLinkForRp(relyingParty = relyingPartyId)
.onFailure {
return Fido2ValidateOriginResult.Error.AssetLinkNotFound
}
.mapCatching { statements ->
statements
.filterMatchingAppStatementsOrNull(
rpPackageName = callingAppInfo.packageName,
)
?: return Fido2ValidateOriginResult.Error.ApplicationNotFound
}
.mapCatching { matchingStatements ->
callingAppInfo
.getSignatureFingerprintAsHexString()
?.let { certificateFingerprint ->
matchingStatements
.filterMatchingAppSignaturesOrNull(
signature = certificateFingerprint,
)
}
?: return Fido2ValidateOriginResult.Error.ApplicationFingerprintNotVerified
}
.fold(
onSuccess = {
Fido2ValidateOriginResult.Success(null)
},
onFailure = {
Fido2ValidateOriginResult.Error.Unknown
},
)
private suspend fun validatePrivilegedAppOrigin(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult {
val googleAllowListResult =
validatePrivilegedAppSignatureWithGoogleList(callingAppInfo)
return when (googleAllowListResult) {
is Fido2ValidateOriginResult.Success -> {
// Application was found and successfully validated against the Google allow list so
// we can return the result as the final validation result.
googleAllowListResult
}
is Fido2ValidateOriginResult.Error -> {
// Check the community allow list if the Google allow list failed, and return the
// result as the final validation result.
validatePrivilegedAppSignatureWithCommunityList(callingAppInfo)
}
}
}
private suspend fun validatePrivilegedAppSignatureWithGoogleList(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult =
validatePrivilegedAppSignatureWithAllowList(
callingAppInfo = callingAppInfo,
fileName = GOOGLE_ALLOW_LIST_FILE_NAME,
)
private suspend fun validatePrivilegedAppSignatureWithCommunityList(
callingAppInfo: CallingAppInfo,
): Fido2ValidateOriginResult =
validatePrivilegedAppSignatureWithAllowList(
callingAppInfo = callingAppInfo,
fileName = COMMUNITY_ALLOW_LIST_FILE_NAME,
)
private suspend fun validatePrivilegedAppSignatureWithAllowList(
callingAppInfo: CallingAppInfo,
fileName: String,
): Fido2ValidateOriginResult =
assetManager
.readAsset(fileName)
.mapCatching { allowList ->
callingAppInfo.validatePrivilegedApp(
allowList = allowList,
)
}
.fold(
onSuccess = { it },
onFailure = {
Timber.e(it, "Failed to validate privileged app: ${callingAppInfo.packageName}")
Fido2ValidateOriginResult.Error.Unknown
},
)
/**
* Returns statements targeting the calling Android application, or null.
*/
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppStatementsOrNull(
rpPackageName: String,
): List<DigitalAssetLinkResponseJson>? =
filter { statement ->
val target = statement.target
target.namespace == "android_app" &&
target.packageName == rpPackageName &&
statement.relation.containsAll(
listOf(
"delegate_permission/common.get_login_creds",
"delegate_permission/common.handle_all_urls",
),
)
}
.takeUnless { it.isEmpty() }
/**
* Returns statements that match the given [signature], or null.
*/
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppSignaturesOrNull(
signature: String,
): List<DigitalAssetLinkResponseJson>? =
filter { statement ->
statement.target.sha256CertFingerprints
?.contains(signature)
?: false
}
.takeUnless { it.isEmpty() }
}

View File

@@ -14,12 +14,13 @@ import kotlinx.parcelize.Parcelize
* @property callingAppInfo Information about the application that initiated the request.
*/
@Parcelize
data class Fido2CredentialRequest(
data class Fido2CreateCredentialRequest(
val userId: String,
val requestJson: String,
val packageName: String,
val signingInfo: SigningInfo,
val origin: String?,
val isUserVerified: Boolean?,
) : Parcelable {
val callingAppInfo: CallingAppInfo
get() = CallingAppInfo(

View File

@@ -7,6 +7,19 @@ import kotlinx.parcelize.Parcelize
/**
* Models a FIDO 2 credential authentication request parsed from the launching intent.
*
* @param userId The ID of the Bitwarden user to authenticate.
* @param cipherId The ID of the cipher that contains the passkey to authenticate.
* @param credentialId The ID of the credential to authenticate.
* @param requestJson The JSON representation of the FIDO 2 request.
* @param clientDataHash The hash of the client data.
* @param packageName The package name of the calling app.
* @param signingInfo The signing info of the calling app.
* @param origin The origin of the calling app. Only populated if the calling application is a
* privileged application. I.e., a web browser.
* @param isUserVerified Whether the user has been verified prior to receiving this request. Only
* populated if device biometric verification was performed. If null, the application is responsible
* for prompting user verification when it is deemed necessary.
*/
@Parcelize
data class Fido2CredentialAssertionRequest(
@@ -18,6 +31,7 @@ data class Fido2CredentialAssertionRequest(
val packageName: String,
val signingInfo: SigningInfo,
val origin: String?,
val isUserVerified: Boolean?,
) : Parcelable {
val callingAppInfo: CallingAppInfo
get() = CallingAppInfo(packageName, signingInfo, origin)

View File

@@ -1,5 +1,7 @@
package com.x8bit.bitwarden.data.autofill.fido2.model
import com.x8bit.bitwarden.ui.platform.base.util.Text
/**
* Represents possible outcomes of a FIDO 2 credential assertion request.
*/
@@ -13,5 +15,5 @@ sealed class Fido2CredentialAssertionResult {
/**
* Indicates there was an error and the assertion was not successful.
*/
data object Error : Fido2CredentialAssertionResult()
data class Error(val message: Text) : Fido2CredentialAssertionResult()
}

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.autofill.fido2.model
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import com.bitwarden.fido.Fido2CredentialAutofillView
import com.x8bit.bitwarden.ui.platform.base.util.Text
/**
* Represents the result of a FIDO 2 Get Credentials request.
@@ -24,5 +25,7 @@ sealed class Fido2GetCredentialsResult {
/**
* Indicates an error was encountered when querying for matching credentials.
*/
data object Error : Fido2GetCredentialsResult()
data class Error(
val message: Text,
) : Fido2GetCredentialsResult()
}

View File

@@ -1,5 +1,7 @@
package com.x8bit.bitwarden.data.autofill.fido2.model
import com.x8bit.bitwarden.ui.platform.base.util.Text
/**
* Models the data returned from creating a FIDO 2 credential.
*/
@@ -9,13 +11,13 @@ sealed class Fido2RegisterCredentialResult {
* Indicates the credential has been successfully registered.
*/
data class Success(
val registrationResponse: String,
val responseJson: String,
) : Fido2RegisterCredentialResult()
/**
* Indicates there was an error and the credential was not registered.
*/
data object Error : Fido2RegisterCredentialResult()
data class Error(val message: Text) : Fido2RegisterCredentialResult()
/**
* Indicates the user cancelled the request.

View File

@@ -1,5 +1,8 @@
package com.x8bit.bitwarden.data.autofill.fido2.model
import androidx.annotation.StringRes
import com.x8bit.bitwarden.R
/**
* Models the result of validating the origin of a FIDO2 request.
*/
@@ -7,49 +10,75 @@ sealed class Fido2ValidateOriginResult {
/**
* Represents a successful origin validation.
*
* @param origin The origin of the calling app, or null if the calling app is not privileged.
*/
data object Success : Fido2ValidateOriginResult()
data class Success(val origin: String?) : Fido2ValidateOriginResult()
/**
* Represents a validation error.
*/
sealed class Error : Fido2ValidateOriginResult() {
/**
* The string resource ID of the error message.
*/
@get:StringRes
abstract val messageResId: Int
/**
* Indicates the digital asset links file could not be located.
*/
data object AssetLinkNotFound : Error()
data object AssetLinkNotFound : Error() {
override val messageResId =
R.string.passkey_operation_failed_because_of_missing_asset_links
}
/**
* Indicates the application package name was not found in the digital asset links file.
*/
data object ApplicationNotFound : Error()
data object ApplicationNotFound : Error() {
override val messageResId =
R.string.passkey_operation_failed_because_app_not_found_in_asset_links
}
/**
* Indicates the application fingerprint was not found the digital asset links file.
*/
data object ApplicationNotVerified : Error()
data object ApplicationFingerprintNotVerified : Error() {
override val messageResId =
R.string.passkey_operation_failed_because_app_could_not_be_verified
}
/**
* Indicates the calling application is privileged but its package name is not found within
* the privileged app allow list.
*/
data object PrivilegedAppNotAllowed : Error()
data object PrivilegedAppNotAllowed : Error() {
override val messageResId =
R.string.passkey_operation_failed_because_browser_is_not_privileged
}
/**
* Indicates the calling app is privileged but but no matching signing certificate signature
* is present in the allow list.
*/
data object PrivilegedAppSignatureNotFound : Error()
data object PrivilegedAppSignatureNotFound : Error() {
override val messageResId =
R.string.passkey_operation_failed_because_browser_signature_does_not_match
}
/**
* Indicates passkeys are not supported for the requesting application.
*/
data object PasskeyNotSupportedForApp : Error()
data object PasskeyNotSupportedForApp : Error() {
override val messageResId = R.string.passkeys_not_supported_for_this_app
}
/**
* Indicates an unknown error was encountered while validating the origin.
*/
data object Unknown : Error()
data object Unknown : Error() {
override val messageResId = R.string.generic_error_message
}
}
}

View File

@@ -13,5 +13,5 @@ data class PublicKeyCredentialDescriptor(
@SerialName("id")
val id: String,
@SerialName("transports")
val transports: List<String>,
val transports: List<String>?,
)

View File

@@ -1,10 +1,13 @@
package com.x8bit.bitwarden.data.autofill.fido2.processor
import android.content.Context
import android.graphics.drawable.Icon
import android.os.Build
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.credentials.exceptions.ClearCredentialException
import androidx.credentials.exceptions.ClearCredentialUnsupportedException
import androidx.credentials.exceptions.CreateCredentialCancellationException
@@ -21,25 +24,35 @@ import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest
import androidx.credentials.provider.BeginGetCredentialRequest
import androidx.credentials.provider.BeginGetCredentialResponse
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import androidx.credentials.provider.BiometricPromptData
import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.CredentialEntry
import androidx.credentials.provider.ProviderClearCredentialStateRequest
import androidx.credentials.provider.PublicKeyCredentialEntry
import com.bitwarden.fido.Fido2CredentialAutofillView
import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DecryptFido2CredentialAutofillViewResult
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.fold
import kotlinx.coroutines.launch
import java.time.Clock
import java.util.concurrent.atomic.AtomicInteger
import javax.crypto.Cipher
private const val CREATE_PASSKEY_INTENT = "com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY"
const val GET_PASSKEY_INTENT = "com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY"
@@ -49,7 +62,7 @@ const val UNLOCK_ACCOUNT_INTENT = "com.x8bit.bitwarden.fido2.ACTION_UNLOCK_ACCOU
* The default implementation of [Fido2ProviderProcessor]. Its purpose is to handle FIDO2 related
* processing.
*/
@Suppress("LongParameterList")
@Suppress("LongParameterList", "TooManyFunctions")
@RequiresApi(Build.VERSION_CODES.S)
class Fido2ProviderProcessorImpl(
private val context: Context,
@@ -59,6 +72,8 @@ class Fido2ProviderProcessorImpl(
private val fido2CredentialManager: Fido2CredentialManager,
private val intentManager: IntentManager,
private val clock: Clock,
private val biometricsEncryptionManager: BiometricsEncryptionManager,
private val featureFlagManager: FeatureFlagManager,
dispatcherManager: DispatcherManager,
) : Fido2ProviderProcessor {
@@ -89,60 +104,6 @@ class Fido2ProviderProcessorImpl(
}
}
private fun processCreateCredentialRequest(
request: BeginCreateCredentialRequest,
): BeginCreateCredentialResponse? {
return when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
handleCreatePasskeyQuery(request)
}
else -> null
}
}
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest,
): BeginCreateCredentialResponse? {
val requestJson = request
.candidateQueryData
.getString("androidx.credentials.BUNDLE_KEY_REQUEST_JSON")
if (requestJson.isNullOrEmpty()) return null
val userState = authRepository.userStateFlow.value ?: return null
return BeginCreateCredentialResponse.Builder()
.setCreateEntries(userState.accounts.toCreateEntries(userState.activeUserId))
.build()
}
private fun List<UserState.Account>.toCreateEntries(activeUserId: String) =
map { it.toCreateEntry(isActive = activeUserId == it.userId) }
private fun UserState.Account.toCreateEntry(isActive: Boolean): CreateEntry {
val accountName = name ?: email
return CreateEntry
.Builder(
accountName = accountName,
pendingIntent = intentManager.createFido2CreationPendingIntent(
CREATE_PASSKEY_INTENT,
userId,
requestCode.getAndIncrement(),
),
)
.setDescription(
context.getString(
R.string.your_passkey_will_be_saved_to_your_bitwarden_vault_for_x,
accountName,
),
)
// Set the last used time to "now" so the active account is the default option in the
// system prompt.
.setLastUsedTime(if (isActive) clock.instant() else null)
.build()
}
override fun processGetCredentialRequest(
request: BeginGetCredentialRequest,
cancellationSignal: CancellationSignal,
@@ -197,6 +158,78 @@ class Fido2ProviderProcessorImpl(
}
}
override fun processClearCredentialStateRequest(
request: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// no-op: RFU
callback.onError(ClearCredentialUnsupportedException())
}
private fun processCreateCredentialRequest(
request: BeginCreateCredentialRequest,
): BeginCreateCredentialResponse? {
return when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
handleCreatePasskeyQuery(request)
}
else -> null
}
}
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest,
): BeginCreateCredentialResponse? {
val requestJson = request
.candidateQueryData
.getString("androidx.credentials.BUNDLE_KEY_REQUEST_JSON")
if (requestJson.isNullOrEmpty()) return null
val userState = authRepository.userStateFlow.value ?: return null
return BeginCreateCredentialResponse.Builder()
.setCreateEntries(userState.accounts.toCreateEntries(userState.activeUserId))
.build()
}
private fun List<UserState.Account>.toCreateEntries(activeUserId: String) =
map { it.toCreateEntry(isActive = activeUserId == it.userId) }
private fun UserState.Account.toCreateEntry(isActive: Boolean): CreateEntry {
val accountName = name ?: email
val entryBuilder = CreateEntry
.Builder(
accountName = accountName,
pendingIntent = intentManager.createFido2CreationPendingIntent(
CREATE_PASSKEY_INTENT,
userId,
requestCode.getAndIncrement(),
),
)
.setDescription(
context.getString(
R.string.your_passkey_will_be_saved_to_your_bitwarden_vault_for_x,
accountName,
),
)
// Set the last used time to "now" so the active account is the default option in the
// system prompt.
.setLastUsedTime(if (isActive) clock.instant() else null)
.setAutoSelectAllowed(true)
if (isVaultUnlocked &&
featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyCreation)
) {
biometricsEncryptionManager
.getOrCreateCipher(userId)
?.let { entryBuilder.setBiometricPromptDataIfSupported(cipher = it) }
}
return entryBuilder.build()
}
@Throws(GetCredentialUnsupportedException::class)
private suspend fun getMatchingFido2CredentialEntries(
userId: String,
@@ -223,10 +256,14 @@ class Fido2ProviderProcessorImpl(
): List<CredentialEntry> {
val cipherViews = vaultRepository
.ciphersStateFlow
.value
.data
?.filter { it.isActiveWithFido2Credentials }
?: emptyList()
.takeUntilLoaded()
.fold(emptyList<CipherView>()) { _, dataState ->
when (dataState) {
is DataState.Loaded -> dataState.data.filter { it.isActiveWithFido2Credentials }
else -> emptyList()
}
}
val result = vaultRepository
.getDecryptedFido2CredentialAutofillViews(cipherViews)
return when (result) {
@@ -252,29 +289,70 @@ class Fido2ProviderProcessorImpl(
): List<CredentialEntry> =
this
.map {
PublicKeyCredentialEntry
val publicKeyEntryBuilder = PublicKeyCredentialEntry
.Builder(
context = context,
username = it.userNameForUi ?: context.getString(R.string.no_username),
pendingIntent = intentManager
.createFido2GetCredentialPendingIntent(
action = GET_PASSKEY_INTENT,
userId = userId,
credentialId = it.credentialId.toString(),
cipherId = it.cipherId,
requestCode = requestCode.getAndIncrement(),
),
pendingIntent = intentManager.createFido2GetCredentialPendingIntent(
action = GET_PASSKEY_INTENT,
userId = userId,
credentialId = it.credentialId.toString(),
cipherId = it.cipherId,
requestCode = requestCode.getAndIncrement(),
),
beginGetPublicKeyCredentialOption = option,
)
.build()
.setIcon(
Icon.createWithResource(
context,
R.drawable.ic_bw_passkey,
),
)
if (featureFlagManager.getFeatureFlag(FlagKey.SingleTapPasskeyAuthentication)) {
biometricsEncryptionManager
.getOrCreateCipher(userId)
?.let {
publicKeyEntryBuilder
.setBiometricPromptDataIfSupported(cipher = it)
}
}
publicKeyEntryBuilder.build()
}
override fun processClearCredentialStateRequest(
request: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// no-op: RFU
callback.onError(ClearCredentialUnsupportedException())
private fun PublicKeyCredentialEntry.Builder.setBiometricPromptDataIfSupported(
cipher: Cipher,
): PublicKeyCredentialEntry.Builder {
return if (isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM)) {
this
} else {
setBiometricPromptData(
biometricPromptData = BiometricPromptData
.Builder()
.buildPromptDataWithCipher(cipher),
)
}
}
private fun CreateEntry.Builder.setBiometricPromptDataIfSupported(
cipher: Cipher,
): CreateEntry.Builder {
return if (isBuildVersionBelow(Build.VERSION_CODES.VANILLA_ICE_CREAM)) {
this
} else {
setBiometricPromptData(
biometricPromptData = BiometricPromptData
.Builder()
.buildPromptDataWithCipher(cipher),
)
}
}
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
private fun BiometricPromptData.Builder.buildPromptDataWithCipher(
cipher: Cipher,
): BiometricPromptData = BiometricPromptData.Builder()
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.setCryptoObject(BiometricPrompt.CryptoObject(cipher))
.build()
}

View File

@@ -6,8 +6,8 @@ import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import androidx.credentials.provider.PendingIntentHandler
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CIPHER_ID
@@ -15,10 +15,10 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CREDENTIAL_ID
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_USER_ID
/**
* Checks if this [Intent] contains a [Fido2CredentialRequest] related to an ongoing FIDO 2
* Checks if this [Intent] contains a [Fido2CreateCredentialRequest] related to an ongoing FIDO 2
* credential creation process.
*/
fun Intent.getFido2CredentialRequestOrNull(): Fido2CredentialRequest? {
fun Intent.getFido2CreateCredentialRequestOrNull(): Fido2CreateCredentialRequest? {
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
val systemRequest = PendingIntentHandler
@@ -33,12 +33,13 @@ fun Intent.getFido2CredentialRequestOrNull(): Fido2CredentialRequest? {
val userId = getStringExtra(EXTRA_KEY_USER_ID)
?: return null
return Fido2CredentialRequest(
return Fido2CreateCredentialRequest(
userId = userId,
requestJson = createPublicKeyRequest.requestJson,
packageName = systemRequest.callingAppInfo.packageName,
signingInfo = systemRequest.callingAppInfo.signingInfo,
origin = systemRequest.callingAppInfo.origin,
isUserVerified = systemRequest.biometricPromptResult?.isSuccessful,
)
}
@@ -67,6 +68,8 @@ fun Intent.getFido2AssertionRequestOrNull(): Fido2CredentialAssertionRequest? {
val userId: String = getStringExtra(EXTRA_KEY_USER_ID)
?: return null
val isUserVerified = systemRequest.biometricPromptResult?.isSuccessful
return Fido2CredentialAssertionRequest(
userId = userId,
cipherId = cipherId,
@@ -76,6 +79,7 @@ fun Intent.getFido2AssertionRequestOrNull(): Fido2CredentialAssertionRequest? {
packageName = systemRequest.callingAppInfo.packageName,
signingInfo = systemRequest.callingAppInfo.signingInfo,
origin = systemRequest.callingAppInfo.origin,
isUserVerified = isUserVerified,
)
}

View File

@@ -2,6 +2,9 @@ package com.x8bit.bitwarden.data.autofill.manager
import android.view.autofill.AutofillManager
import androidx.lifecycle.LifecycleCoroutineScope
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillManager
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -11,19 +14,31 @@ import kotlinx.coroutines.flow.onEach
*/
class AutofillActivityManagerImpl(
private val autofillManager: AutofillManager,
private val autofillEnabledManager: AutofillEnabledManager,
private val chromeThirdPartyAutofillManager: ChromeThirdPartyAutofillManager,
autofillEnabledManager: AutofillEnabledManager,
appStateManager: AppStateManager,
lifecycleScope: LifecycleCoroutineScope,
chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager,
) : AutofillActivityManager {
private val isAutofillEnabledAndSupported: Boolean
get() = autofillManager.isEnabled &&
autofillManager.hasEnabledAutofillServices() &&
autofillManager.isAutofillSupported
private val chromeAutofillStatus: ChromeThirdPartyAutofillStatus
get() = ChromeThirdPartyAutofillStatus(
stableStatusData = chromeThirdPartyAutofillManager.stableChromeAutofillStatus,
betaChannelStatusData = chromeThirdPartyAutofillManager.betaChromeAutofillStatus,
)
init {
appStateManager
.appForegroundStateFlow
.onEach { autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported }
.onEach {
autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported
chromeThirdPartyAutofillEnabledManager.chromeThirdPartyAutofillStatus =
chromeAutofillStatus
}
.launchIn(lifecycleScope)
}
}

View File

@@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardMan
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.ui.platform.base.util.asText
import java.time.Clock
/**
@@ -34,7 +35,10 @@ class AutofillTotpManagerImpl(
)
if (totpResult is GenerateTotpResult.Success) {
clipboardManager.setText(text = totpResult.code)
clipboardManager.setText(
text = totpResult.code,
toastDescriptorOverride = R.string.verification_code_totp.asText(),
)
Toast
.makeText(
context.applicationContext,

View File

@@ -0,0 +1,22 @@
package com.x8bit.bitwarden.data.autofill.manager.chrome
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/**
* Manager which provides whether specific Chrome versions have third party autofill available and
* enabled.
*/
interface ChromeThirdPartyAutofillEnabledManager {
/**
* Combined status for all concerned Chrome versions.
*/
var chromeThirdPartyAutofillStatus: ChromeThirdPartyAutofillStatus
/**
* An observable [StateFlow] of the combined third party autofill status of all concerned
* chrome versions.
*/
val chromeThirdPartyAutofillStatusFlow: Flow<ChromeThirdPartyAutofillStatus>
}

View File

@@ -0,0 +1,52 @@
package com.x8bit.bitwarden.data.autofill.manager.chrome
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
/**
* Default implementation of [ChromeThirdPartyAutofillEnabledManager].
*/
class ChromeThirdPartyAutofillEnabledManagerImpl(
private val featureFlagManager: FeatureFlagManager,
) : ChromeThirdPartyAutofillEnabledManager {
override var chromeThirdPartyAutofillStatus: ChromeThirdPartyAutofillStatus = DEFAULT_STATUS
set(value) {
field = value
mutableChromeThirdPartyAutofillStatusStateFlow.update {
value
}
}
private val mutableChromeThirdPartyAutofillStatusStateFlow = MutableStateFlow(
chromeThirdPartyAutofillStatus,
)
override val chromeThirdPartyAutofillStatusFlow: Flow<ChromeThirdPartyAutofillStatus>
get() = mutableChromeThirdPartyAutofillStatusStateFlow
.combine(
featureFlagManager.getFeatureFlagFlow(FlagKey.ChromeAutofill),
) { data, enabled ->
if (enabled) {
data
} else {
DEFAULT_STATUS
}
}
}
private val DEFAULT_STATUS = ChromeThirdPartyAutofillStatus(
ChromeThirdPartyAutoFillData(
isAvailable = false,
isThirdPartyEnabled = false,
),
ChromeThirdPartyAutoFillData(
isAvailable = false,
isThirdPartyEnabled = false,
),
)

View File

@@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.autofill.manager.chrome
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
/**
* Manager class used to determine if a device has installed versions of Chrome (either the
* stable release or beta channel) which support and require opt in to third party autofill.
*/
interface ChromeThirdPartyAutofillManager {
/**
* The data representing the status of the stable chrome version
*/
val stableChromeAutofillStatus: ChromeThirdPartyAutoFillData
/**
* The data representing the status of the beta chrome version
*/
val betaChromeAutofillStatus: ChromeThirdPartyAutoFillData
}

View File

@@ -0,0 +1,62 @@
package com.x8bit.bitwarden.data.autofill.manager.chrome
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
private const val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"
private const val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"
private const val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"
/**
* Default implementation of the [ChromeThirdPartyAutofillManager] which uses a
* [ContentResolver] to determine if the installed Chrome packages support and enable
* third party autofill services.
*
* Based off of [this blog post](https://android-developers.googleblog.com/2025/02/chrome-3p-autofill-services-update.html)
*/
@OmitFromCoverage
class ChromeThirdPartyAutofillManagerImpl(
private val context: Context,
) : ChromeThirdPartyAutofillManager {
override val stableChromeAutofillStatus: ChromeThirdPartyAutoFillData
get() = getThirdPartyAutoFillStatusForChannel(ChromeReleaseChannel.STABLE)
override val betaChromeAutofillStatus: ChromeThirdPartyAutoFillData
get() = getThirdPartyAutoFillStatusForChannel(ChromeReleaseChannel.BETA)
private fun getThirdPartyAutoFillStatusForChannel(
releaseChannel: ChromeReleaseChannel,
): ChromeThirdPartyAutoFillData {
val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(releaseChannel.packageName + CONTENT_PROVIDER_NAME)
.path(THIRD_PARTY_MODE_ACTIONS_URI_PATH)
.build()
val cursor = context
.contentResolver
.query(
/* uri = */ uri,
/* projection = */ arrayOf(THIRD_PARTY_MODE_COLUMN),
/* selection = */ null,
/* selectionArgs = */ null,
/* sortOrder = */ null,
)
var thirdPartyEnabled = false
val isThirdPartyAvailable = cursor
?.let {
it.moveToFirst()
val columnIndex = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN)
thirdPartyEnabled = it.getInt(columnIndex) != 0
it.close()
true
}
?: false
return ChromeThirdPartyAutoFillData(
isAvailable = isThirdPartyAvailable,
isThirdPartyEnabled = thirdPartyEnabled,
)
}
}

View File

@@ -0,0 +1,14 @@
package com.x8bit.bitwarden.data.autofill.model.chrome
private const val BETA_CHANNEL_PACKAGE = "com.chrome.beta"
private const val CHROME_CHANNEL_PACKAGE = "com.android.chrome"
/**
* Enumerated values of each version of Chrome supported for third party autofill checks.
*
* @property packageName the package name of the release channel for the Chrome version.
*/
enum class ChromeReleaseChannel(val packageName: String) {
STABLE(CHROME_CHANNEL_PACKAGE),
BETA(BETA_CHANNEL_PACKAGE),
}

View File

@@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.autofill.model.chrome
/**
* Relevant data relating to the third party autofill status of a version of the Chrome browser app.
*/
data class ChromeThirdPartyAutoFillData(
val isAvailable: Boolean,
val isThirdPartyEnabled: Boolean,
)
/**
* The overall status for all relevant release channels of Chrome.
*/
data class ChromeThirdPartyAutofillStatus(
val stableStatusData: ChromeThirdPartyAutoFillData,
val betaChannelStatusData: ChromeThirdPartyAutoFillData,
)

View File

@@ -2,6 +2,7 @@
package com.x8bit.bitwarden.data.autofill.util
import android.app.Activity
import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
@@ -147,3 +148,12 @@ fun Intent.getAutofillSelectionDataOrNull(): AutofillSelectionData? =
fun Intent.getTotpCopyIntentOrNull(): AutofillTotpCopyData? =
getBundleExtra(AUTOFILL_BUNDLE_KEY)
?.getSafeParcelableExtra(AUTOFILL_TOTP_COPY_DATA_KEY)
/**
* Checks if the given [Activity] was created for Autofill. This is useful to avoid locking the
* vault if one of the Autofill services starts the only instance of the [MainActivity].
*/
val Activity.createdForAutofill: Boolean
get() = intent.getAutofillSelectionDataOrNull() != null ||
intent.getAutofillSaveItemOrNull() != null ||
intent.getAutofillAssistStructureOrNull() != null

View File

@@ -24,7 +24,6 @@ fun AutofillRequest.Fillable.toAutofillSaveItem(): AutofillSaveItem =
.uri
?.replace("https://", "")
?.replace("http://", "")
?.replace("androidapp://", "")
AutofillSaveItem.Login(
username = partition.usernameSaveValue,

View File

@@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
private const val SERVER_CONFIGURATIONS = "serverConfigurations"

View File

@@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
private const val PRE_AUTH_URLS_KEY = "preAuthEnvironmentUrls"

View File

@@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEv
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEventType
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
/**

View File

@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
@@ -18,6 +19,11 @@ interface SettingsDiskSource {
*/
var appLanguage: AppLanguage?
/**
* Emits updates that track [AppLanguage].
*/
val appLanguageFlow: Flow<AppLanguage?>
/**
* Has the initial autofill dialog been shown to the user.
*/
@@ -68,17 +74,6 @@ interface SettingsDiskSource {
*/
val hasUserLoggedInOrCreatedAccountFlow: Flow<Boolean?>
/**
* The instant when the last database scheme change was applied. `null` if no scheme changes
* have been applied yet.
*/
var lastDatabaseSchemeChangeInstant: Instant?
/**
* Emits updates that track [lastDatabaseSchemeChangeInstant].
*/
val lastDatabaseSchemeChangeInstantFlow: Flow<Instant?>
/**
* Clears all the settings data for the given user.
*/
@@ -319,4 +314,92 @@ interface SettingsDiskSource {
* Emits updates that track [getShowImportLoginsSettingBadge] for the given [userId].
*/
fun getShowImportLoginsSettingBadgeFlow(userId: String): Flow<Boolean?>
/**
* Gets whether or not the given [userId] has registered for export via the credential exchange
* protocol.
*/
fun getVaultRegisteredForExport(userId: String): Boolean?
/**
* Stores the given value for whether or not the given [userId] has registered for export via
* the credential exchange protocol.
*/
fun storeVaultRegisteredForExport(userId: String, isRegistered: Boolean?)
/**
* Emits updates that track [getVaultRegisteredForExport] for the given [userId].
*/
fun getVaultRegisteredForExportFlow(userId: String): Flow<Boolean?>
/**
* Gets the number of qualifying add cipher actions for the device.
*/
fun getAddCipherActionCount(): Int?
/**
* Stores the given [count] completed "add" cipher actions taken place on the device.
*/
fun storeAddCipherActionCount(count: Int?)
/**
* Gets the number of qualifying generated result actions for the device.
*/
fun getGeneratedResultActionCount(): Int?
/**
* Stores the given [count] completed generated password or username result actions taken
* for the device.
*/
fun storeGeneratedResultActionCount(count: Int?)
/**
* Gets the number of qualifying create send actions for the device.
*/
fun getCreateSendActionCount(): Int?
/**
* Stores the given [count] completed create send actions for the device.
*/
fun storeCreateSendActionCount(count: Int?)
/**
* Gets the Boolean value of if the Add Login CoachMark tour has been interacted with.
*/
fun getShouldShowAddLoginCoachMark(): Boolean?
/**
* Stores a value for if the Add Login CoachMark tour has been interacted with
*/
fun storeShouldShowAddLoginCoachMark(shouldShow: Boolean?)
/**
* Returns an [Flow] to observe updates to the "ShouldShowAddLoginCoachMark" value.
*/
fun getShouldShowAddLoginCoachMarkFlow(): Flow<Boolean?>
/**
* Gets the Boolean value of if the Generator CoachMark tour has been interacted with.
*/
fun getShouldShowGeneratorCoachMark(): Boolean?
/**
* Stores a value for if the Generator CoachMark tour has been interacted with
*/
fun storeShouldShowGeneratorCoachMark(shouldShow: Boolean?)
/**
* Returns an [Flow] to observe updates to the "ShouldShowGeneratorCoachMark" value.
*/
fun getShouldShowGeneratorCoachMarkFlow(): Flow<Boolean?>
/**
* Stores the given [screenData] as the screen to resume to identified by [userId].
*/
fun storeAppResumeScreen(userId: String, screenData: AppResumeScreenData?)
/**
* Gets the screen data to resume to for the device identified by [userId] or null if no screen
*/
fun getAppResumeScreen(userId: String): AppResumeScreenData?
}

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