Compare commits

...

529 Commits

Author SHA1 Message Date
Álison Fernandes
bf2ac7cac9 Add pyproject.toml 2025-03-03 16:57:12 +00:00
Álison Fernandes
5bbbf7c5e4 Add script and tests for linked issues, removing method from the release notes script 2025-03-03 16:52:43 +00:00
Álison Fernandes
39d10fa77d Add release notes processing script and unit tests 2025-03-03 15:44:11 +00:00
Álison Fernandes
f0f240f8e5 Update gitignore for python 2025-03-03 15:43:27 +00:00
aj-rosado
ac6ff98041 [PM-14435] Accessibility enabled settings changes to address older and custom Android phone versions (#4756) 2025-02-28 22:25:11 +00:00
David Perez
ec030f2c2e Update AGP to 8.8.2 (#4809) 2025-02-28 18:39:52 +00:00
Patrick Honkonen
be08c1a536 Refactor .editorconfig to focus on Kotlin and common file types (#4808) 2025-02-28 16:16:28 +00:00
bw-ghapp[bot]
84edf4ead0 Autosync Crowdin Translations (#4806)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2025-02-28 14:34:04 +00:00
aj-rosado
915aac561c [PM-17215] Remove get_login_creds from Fido2OriginManagerImpl.filterMatchingAppStatementsOrNull (#4804) 2025-02-28 10:46:04 +00:00
Patrick Honkonen
2027a66f02 [PM-18714] Display Card brand icon when it is known (#4805) 2025-02-27 21:57:28 +00:00
David Perez
ef6d9bc68c PM-18677: Policies for disabled organizations apply (#4801) 2025-02-27 21:54:47 +00:00
Dave Severns
3ceda9e40a PM-18388 add hyphens on segmented button labels (#4777) 2025-02-27 21:50:37 +00:00
Phil Cappelli
3f1a6e97fd PM-17568 - Authenticator Sync: Sometimes synced verification codes only display the TOTP Key, not Issuer/username (#4783) 2025-02-27 21:42:38 +00:00
André Bispo
4448ab05ce [PM-17739] Show password history (#4803) 2025-02-27 19:49:57 +00:00
aj-rosado
d584391843 [PM-8223] 🍒 New device verification continue button enabled at 8 digit (#4802) 2025-02-27 19:48:24 +00:00
Patrick Honkonen
e0d91d7682 [PM-18067] Consolidate item name fields into ItemHeader (#4766) 2025-02-27 17:31:39 +00:00
Patrick Honkonen
e8a98dd3ed Add spacers to VaultItemIdentityContent, VaultItemSecureNoteContent, and VaultItemCardContent 2025-02-27 12:05:09 -05:00
Patrick Honkonen
b6163cf53c Fix item key in VaultItemCardContent for security code field 2025-02-27 11:14:40 -05:00
Patrick Honkonen
a24c6f2719 Update ic_organization.xml vector drawable 2025-02-27 11:13:04 -05:00
Patrick Honkonen
b3219d4040 Use CardStyle.Bottom when last item is drawn 2025-02-27 11:05:06 -05:00
Patrick Honkonen
27043e28a8 Revert formatting changes 2025-02-27 10:30:41 -05:00
Patrick Honkonen
7c36b7cb82 [PM-14303] Update Bitwarden SDK and load bitwarden_uniffi on older Android versions (#4793) 2025-02-27 15:23:55 +00:00
Patrick Honkonen
fcfcf48cee Uncomment & update previews 2025-02-27 09:17:50 -05:00
Patrick Honkonen
757994ec18 Merge remote-tracking branch 'origin/main' into PM-18067/view-item-favicon 2025-02-27 09:17:16 -05:00
Álison Fernandes
548de56d60 [PM-18434] Welcome Authenticator app! (#4798) 2025-02-27 12:54:51 +00:00
Patrick Honkonen
71cd917328 Merge branch 'main' into PM-14303/update-bitwarden-sdk 2025-02-26 17:15:35 -05:00
Álison Fernandes
01188c21b2 Update BWA secret names 2025-02-26 22:14:56 +00:00
Patrick Honkonen
5c076871ab Implement NativeLibraryManager and NativeLibraryManagerImpl for loading native libraries
This commit introduces the `NativeLibraryManager` interface and its implementation, `NativeLibraryManagerImpl`.
- `NativeLibraryManager` defines a method `loadLibrary` for loading native libraries.
- `NativeLibraryManagerImpl` uses `System.loadLibrary` to load libraries and handles potential `UnsatisfiedLinkError`.
- `PlatformManagerModule` is updated to provide `NativeLibraryManager` instance and to inject it into `SdkClientManager`.
2025-02-26 17:14:51 -05: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
Patrick Honkonen
07d3849c4b Add keys and animation to all content items 2025-02-26 17:10:10 -05:00
David Perez
33c3fd28e9 itemHeader as LazyListScope extension 2025-02-26 14:45:25 -06:00
Patrick Honkonen
88609c2f5b Update OrganizationType.OWNER mock data in VaultItemViewModelTest.kt 2025-02-26 14:45:25 -06:00
Patrick Honkonen
af8cfcd2f0 Move expansion indicator outside of crossfade animation 2025-02-26 14:45:25 -06:00
Patrick Honkonen
7cc8108498 Adjust ItemHeader expanding header fill the available width 2025-02-26 14:45:25 -06:00
Patrick Honkonen
a31c499b15 Remove unnecessary mockkStatic call in CipherViewExtensionsTest.kt 2025-02-26 14:45:24 -06:00
Patrick Honkonen
d7d099477f Refactor ItemHeader to use LazyColumn and Crossfade for smoother transitions
- Migrates `ItemHeader` to `LazyColumn` to improve performance.
- Introduces `Crossfade` for animating title changes in `BitwardenExpandingHeader`.
- Adjusts icon sizing in `ItemHeaderIcon`.
- Removes unnecessary column scope and animated visibility from `ExpandingItemLocationContent`.
- Refactors to use `LazyItemScope` and adds `animateItem()` to `ItemLocationListItem`.
- Adds conditional handling for expanding the item locations list.
2025-02-26 14:45:24 -06:00
Patrick Honkonen
0ba240852f Refactor ExpandingItemLocationContent to use ColumnScope 2025-02-26 14:45:24 -06:00
Patrick Honkonen
727d943fae Use persistentListOfNotNull instead of buildList and toImmutableList 2025-02-26 14:45:24 -06:00
Patrick Honkonen
234f49a92c Replace HorizontalDivider with BitwardenHorizontalDivider and add Spacer in ItemHeader.kt 2025-02-26 14:45:24 -06:00
Patrick Honkonen
4f49d3d504 Reduce Spacer height in VaultItemLoginContent.kt from 24.dp to 12.dp 2025-02-26 14:45:24 -06:00
Patrick Honkonen
aba8344df1 Revert unintentional change 2025-02-26 14:45:23 -06:00
Patrick Honkonen
6da8e2c47b Adjust height of Spacer in BitwardenTextField based on cardStyle presence 2025-02-26 14:45:23 -06:00
Patrick Honkonen
78d5965271 Refactor ItemHeader to use LazyColumn for overflow locations 2025-02-26 14:45:23 -06:00
Patrick Honkonen
6953d5e132 Migrate VaultItem related locations to ImmutableList 2025-02-26 14:45:23 -06:00
Patrick Honkonen
7073124495 Make cardStyle parameter optional in BitwardenTextField 2025-02-26 14:45:23 -06:00
Patrick Honkonen
0cc7067808 Add divider to ItemHeader in vault item view 2025-02-26 14:45:23 -06:00
Patrick Honkonen
9e920f1cf5 Refactor ItemHeader to use cardStyle and remove custom card styling. 2025-02-26 14:45:22 -06:00
Patrick Honkonen
537e743891 Replaced standardHorizontalMargin with explicit horizontal padding 2025-02-26 14:45:22 -06:00
Patrick Honkonen
60da236f3e Add illustration colors 2025-02-26 14:45:22 -06:00
Patrick Honkonen
7804d8430f [PM-18067] Consolidate item name fields into ItemHeader
This commit introduces `ItemHeader`, a new composable that replaces `ItemNameField` to display the item name, favorite status, and related details like organization, collections, and folder.

Key changes:
- Removes `ItemNameField`
- Adds `ItemHeader` for displaying item name and favorite status, along with item location information.
- Introduces a new `ic_organization` icon.
- Adds the logic for showing item locations (organization, collections, folders) in a collapsible view.
- Removes `ItemNameField` from `VaultItemLoginContent`, `VaultItemIdentityContent`, `VaultItemSecureNoteContent`, `VaultItemCardContent`, `VaultItemSshKeyContent` and replace it with `ItemHeader`
- Adds the logic to fetch and display the item icon in `ItemHeader` based on item type
- Adds an `ItemLocationListItem` for displaying location details.
- Adds a `VaultItemLocation` data class for representing item locations.
- Adds new `baseIconUrl` and `isIconLoadingDisabled` variables to the `VaultItemState` to handle icon display.
- Updates `CipherView.toIconData` to handle the item icon.
- Adds new `show_more`, `no_folder` and `show_less` string resources.
- Updates the `BitwardenShapes` to include `favicon` shapes.
- Updates the `BitwardenColorScheme` to include `faviconForeground` and `faviconBackground`.
- Updates `BitwardenExpandingHeader` to include expandedText, collapsedText and showExpansionIndicator properties.
2025-02-26 14:45:22 -06: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
Patrick Honkonen
dab06b0ed4 [PM-14303] Update Bitwarden SDK and load bitwarden_uniffi on older Android versions
The Bitwarden SDK dependency was updated to version 1.0.0-20250225.125021-120.

The SDK requires access to Android APIs that were made public in API 31 in order to generate email aliases. To address this limitation for devices with earlier API versions, the `bitwarden_uniffi` library is now loaded manually before initializing any `Client` instance.
2025-02-26 10:04:24 -05: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
bw-ghapp[bot]
40056f3181 Autosync Crowdin Translations (#306) 2025-01-07 13:58:44 +00:00
Phil Cappelli
0ca97de1b2 BWA-118 Tutorial text cut off in landscape mode (#312) 2025-01-06 14:55:21 -05: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
Patrick Honkonen
9635bd9b43 [BWA-33]: Publish release bundles to Play Store when requested (#303) 2024-12-13 13:36:54 -05:00
bw-ghapp[bot]
8dd9ba65d0 Autosync Crowdin Translations (#302) 2024-12-13 12:48:37 -05:00
Phil Cappelli
ad90785c39 BWA-124 - 'Copy' Option Missing from Long-Press Menu (#300) 2024-12-12 00:28:59 -05: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
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
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
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
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
renovate[bot]
7d1e2fec90 [deps]: Update gh minor (#284) 2024-12-03 15:23:04 +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
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
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
1164 changed files with 78158 additions and 12383 deletions

View File

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

View File

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

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.

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

View File

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

View File

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

View File

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

View File

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

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

@@ -68,7 +68,7 @@ jobs:
java-version: ${{ env.JAVA_VERSION }}
- name: Configure Ruby
uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 # v1.207.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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@4a9ddd6f338a97768b8006bf671dfbad383215f4 # v1.207.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
@@ -253,7 +253,7 @@ jobs:
- name: Upload release Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.aab
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab
@@ -261,7 +261,7 @@ jobs:
- name: Upload beta Play Store .aab artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.beta.aab
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab
@@ -269,7 +269,7 @@ jobs:
- name: Upload release .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.apk
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
@@ -277,7 +277,7 @@ jobs:
- name: Upload beta .apk artifact
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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.beta.apk
@@ -286,7 +286,7 @@ jobs:
# When building variants other than 'prod'
- name: Upload debug .apk artifact
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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.dev.apk
@@ -324,7 +324,7 @@ jobs:
- name: Upload .apk SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.apk-sha256.txt
path: ./com.x8bit.bitwarden.apk-sha256.txt
@@ -332,7 +332,7 @@ jobs:
- name: Upload .apk SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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
@@ -340,7 +340,7 @@ jobs:
- name: Upload .aab SHA file for release
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: com.x8bit.bitwarden.aab-sha256.txt
path: ./com.x8bit.bitwarden.aab-sha256.txt
@@ -348,7 +348,7 @@ jobs:
- name: Upload .aab SHA file for beta
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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
@@ -356,7 +356,7 @@ jobs:
- name: Upload .apk SHA file for debug
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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
@@ -405,7 +405,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Configure Ruby
uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 # v1.207.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
@@ -515,7 +515,7 @@ jobs:
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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.apk
@@ -527,14 +527,14 @@ jobs:
> ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
- name: Upload F-Droid SHA file
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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.beta-fdroid.apk
@@ -546,7 +546,7 @@ jobs:
> ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
- name: Upload F-Droid Beta SHA file
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
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

@@ -36,7 +36,7 @@ jobs:
private-key: ${{ secrets.BW_GHAPP_KEY }}
- name: Download translations
uses: crowdin/github-action@8dfaf9c206381653e3767e3cb5ea5f08b45f02bf # v2.5.0
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

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

@@ -29,7 +29,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload sources
uses: crowdin/github-action@8dfaf9c206381653e3767e3cb5ea5f08b45f02bf # v2.5.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

@@ -95,7 +95,7 @@ jobs:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
with:
tag_name: "v${{ inputs.version-name }}"
name: "${{ inputs.version-name }} (${{ inputs.version-number }})"

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 }}

View File

@@ -34,7 +34,7 @@ jobs:
--output-path .
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
with:
sarif_file: cx_result.sarif
@@ -58,3 +58,4 @@ jobs:
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

@@ -4,8 +4,6 @@ on:
workflow_dispatch:
pull_request_target:
types: [opened, synchronize]
merge_group:
types: [checks_requested]
jobs:
check-run:
@@ -43,7 +41,7 @@ jobs:
--output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
with:
sarif_file: cx_result.sarif
@@ -70,3 +68,4 @@ jobs:
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

@@ -51,7 +51,7 @@ jobs:
${{ runner.os }}-build-
- name: Configure Ruby
uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4 # v1.207.0
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
with:
bundler-cache: true
@@ -74,7 +74,7 @@ jobs:
bundle exec fastlane check
- name: Upload test reports
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
if: always()
with:
name: test-reports
@@ -90,17 +90,19 @@ jobs:
contents: read
issues: write
pull-requests: write
if: always()
if: success()
steps:
- name: Download test artifacts
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
if: github.event_name == 'push' || github.event_name == 'pull_request'
with:
name: test-reports
- name: Upload to codecov.io
id: upload-to-codecov
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
if: github.event_name == 'push' || github.event_name == 'pull_request'
continue-on-error: true
with:
os: linux
@@ -108,7 +110,7 @@ jobs:
fail_ci_if_error: true
- name: Comment PR if tests failed
if: steps.upload-to-codecov.outcome == 'failure'
if: steps.upload-to-codecov.outcome == 'failure' && (github.event_name == 'push' || github.event_name == 'pull_request')
env:
PR_NUMBER: ${{ github.event.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

9
.gitignore vendored
View File

@@ -25,5 +25,12 @@ 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
# python
__pycache__/
*.py[cod]
*$py.class
*.so

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.1027.0)
aws-sdk-core (3.214.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.96.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.176.1)
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)
@@ -68,7 +68,7 @@ GEM
faraday-retry (1.0.3)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.3.1)
fastimage (2.4.0)
fastlane (2.226.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
@@ -111,7 +111,7 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.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)
@@ -163,7 +163,7 @@ GEM
httpclient (2.8.3)
jmespath (1.6.2)
json (2.9.1)
jwt (2.9.3)
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)
@@ -185,7 +185,7 @@ GEM
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)

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

@@ -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

@@ -325,6 +325,7 @@ kover {
"*_*Factory\$*",
"*.Hilt_*",
"*_HiltModules",
"*_HiltModules*",
"*_HiltModules\$*",
"*_Impl",
"*_Impl\$*",

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

@@ -12,6 +12,20 @@
]
}
},
{
"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": {
@@ -36,6 +50,18 @@
]
}
},
{
"type": "android",
"info": {
"package_name": "org.ironfoxoss.ironfox",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C5:E2:91:B5:A5:71:F9:C8:CD:9A:97:99:C2:C9:4E:02:EC:97:03:94:88:93:F2:CA:75:6D:67:B9:42:04:F9:04"
}
]
}
},
{
"type": "android",
"info": {
@@ -59,34 +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"
}
]
}
}
]
}

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,6 +11,7 @@ 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
@@ -19,6 +20,7 @@ import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityComp
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
@@ -53,6 +55,7 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var debugLaunchManager: DebugMenuLaunchManager
@Suppress("LongMethod")
override fun onCreate(savedInstanceState: Bundle?) {
var shouldShowSplashScreen = true
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
@@ -66,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()
@@ -94,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(
fido2CreateCredentialRequest = 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

@@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJso
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.
@@ -352,4 +353,14 @@ interface AuthDiskSource {
* 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

@@ -15,8 +15,8 @@ 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
@@ -50,6 +50,7 @@ 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].
@@ -155,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.
@@ -503,6 +505,19 @@ class AuthDiskSourceImpl(
)
}
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

@@ -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

@@ -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

@@ -46,6 +46,7 @@ interface IdentityService {
authModel: IdentityTokenAuthModel,
captchaToken: String?,
twoFactorData: TwoFactorDataModel? = null,
newDeviceOtp: String? = null,
): Result<GetTokenResponseJson>
/**

View File

@@ -16,6 +16,7 @@ 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
@@ -34,7 +35,6 @@ class IdentityServiceImpl(
.preLogin(PreLoginRequestJson(email = email))
.toResult()
@Suppress("MagicNumber")
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
unauthenticatedIdentityApi
.register(body)
@@ -43,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",
@@ -79,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(
@@ -102,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,
@@ -114,7 +131,6 @@ class IdentityServiceImpl(
.executeForNetworkResult()
.toResult()
@Suppress("MagicNumber")
override suspend fun registerFinish(
body: RegisterFinishRequestJson,
): Result<RegisterResponseJson> =
@@ -125,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
@@ -142,7 +161,7 @@ class IdentityServiceImpl(
throwable
.toBitwardenError()
.parseErrorBodyOrNull<SendVerificationEmailResponseJson.Invalid>(
code = 400,
code = NetworkErrorCode.BAD_REQUEST,
json = json,
)
?: throw throwable
@@ -161,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

@@ -230,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.
*/
@@ -252,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.
*/
@@ -362,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.
@@ -401,7 +421,7 @@ 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.

View File

@@ -17,11 +17,13 @@ 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
@@ -224,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
/**
@@ -683,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,
@@ -764,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
@@ -873,14 +910,7 @@ class AuthRepositoryImpl(
}
is RegisterResponseJson.Invalid -> {
RegisterResult.Error(
errorMessage = it
.validationErrors
?.values
?.firstOrNull()
?.firstOrNull()
?: it.message,
)
RegisterResult.Error(errorMessage = it.message)
}
}
},
@@ -1088,6 +1118,7 @@ class AuthRepositoryImpl(
}
is VaultUnlockResult.AuthenticationError,
VaultUnlockResult.BiometricDecodingError,
VaultUnlockResult.InvalidStateError,
VaultUnlockResult.GenericError,
-> {
@@ -1162,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) {
@@ -1195,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(
@@ -1333,8 +1377,13 @@ 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? {
@@ -1372,6 +1421,7 @@ class AuthRepositoryImpl(
// 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
@@ -1577,6 +1627,7 @@ class AuthRepositoryImpl(
deviceData: DeviceDataModel? = null,
orgIdentifier: String? = null,
captchaToken: String?,
newDeviceOtp: String? = null,
): LoginResult = identityService
.getToken(
uniqueAppId = authDiskSource.uniqueAppId,
@@ -1584,6 +1635,7 @@ class AuthRepositoryImpl(
authModel = authModel,
twoFactorData = twoFactorData ?: getRememberedTwoFactorData(email),
captchaToken = captchaToken,
newDeviceOtp = newDeviceOtp,
)
.fold(
onFailure = { throwable ->
@@ -1592,6 +1644,7 @@ class AuthRepositoryImpl(
configDiskSource.serverConfig?.isOfficialBitwardenServer == false -> {
LoginResult.UnofficialServerError
}
else -> LoginResult.Error(errorMessage = null)
}
},
@@ -1616,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,
)
}
}
}
}
},
)
@@ -1706,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
@@ -1743,6 +1800,7 @@ class AuthRepositoryImpl(
twoFactorResponse = null
resendEmailRequestJson = null
twoFactorDeviceData = null
resendNewDeviceOtpRequestJson = null
settingsRepository.setDefaultsIfNecessary(userId = userId)
settingsRepository.storeUserHasLoggedInValue(userId)
vaultRepository.syncIfNecessary()
@@ -1775,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

@@ -33,4 +33,9 @@ sealed class 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

@@ -18,5 +18,4 @@ data class Organization(
val shouldManageResetPassword: Boolean,
val shouldUseKeyConnector: Boolean,
val role: OrganizationType,
val shouldUsersGetPremium: Boolean,
)

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

@@ -22,7 +22,6 @@ fun SyncResponseJson.Profile.Organization.toOrganization(): Organization =
shouldUseKeyConnector = this.shouldUseKeyConnector,
role = this.type,
shouldManageResetPassword = this.permissions.shouldManageResetPassword,
shouldUsersGetPremium = this.shouldUsersGetPremium,
)
/**

View File

@@ -1,8 +1,10 @@
package com.x8bit.bitwarden.data.autofill.accessibility
import android.accessibilityservice.AccessibilityService
import android.content.Intent
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.Keep
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
import com.x8bit.bitwarden.data.autofill.accessibility.processor.BitwardenAccessibilityProcessor
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService
@@ -21,9 +23,23 @@ class BitwardenAccessibilityService : AccessibilityService() {
@Inject
lateinit var processor: BitwardenAccessibilityProcessor
@Inject
lateinit var accessibilityEnabledManager: AccessibilityEnabledManager
override fun onAccessibilityEvent(event: AccessibilityEvent) {
processor.processAccessibilityEvent(event = event) { rootInActiveWindow }
}
override fun onInterrupt() = Unit
override fun onUnbind(intent: Intent?): Boolean {
return super
.onUnbind(intent)
.also { accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings() }
}
override fun onServiceConnected() {
super.onServiceConnected()
accessibilityEnabledManager.refreshAccessibilityEnabledFromSettings()
}
}

View File

@@ -57,10 +57,10 @@ object AccessibilityModule {
@Singleton
@Provides
fun providesAccessibilityEnabledManager(
accessibilityManager: AccessibilityManager,
@ApplicationContext context: Context,
): AccessibilityEnabledManager =
AccessibilityEnabledManagerImpl(
accessibilityManager = accessibilityManager,
context = context,
)
@Singleton

View File

@@ -10,4 +10,9 @@ interface AccessibilityEnabledManager {
* Emits updates that track whether the accessibility autofill service is enabled..
*/
val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
/**
* Gets the accessibility enabled state from the system settings.
*/
fun refreshAccessibilityEnabledFromSettings()
}

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.autofill.accessibility.manager
import android.view.accessibility.AccessibilityManager
import android.content.Context
import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -9,18 +10,20 @@ import kotlinx.coroutines.flow.asStateFlow
* The default implementation of [AccessibilityEnabledManager].
*/
class AccessibilityEnabledManagerImpl(
accessibilityManager: AccessibilityManager,
private val context: Context,
) : AccessibilityEnabledManager {
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(value = false)
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(
value = context.isAccessibilityServiceEnabled,
)
init {
accessibilityManager.addAccessibilityStateChangeListener(
AccessibilityManager.AccessibilityStateChangeListener { isEnabled ->
mutableIsAccessibilityEnabledStateFlow.value = isEnabled
},
)
mutableIsAccessibilityEnabledStateFlow.value = context.isAccessibilityServiceEnabled
}
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
override fun refreshAccessibilityEnabledFromSettings() {
mutableIsAccessibilityEnabledStateFlow.value = context.isAccessibilityServiceEnabled
}
}

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

@@ -13,6 +13,8 @@ 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
@@ -44,6 +46,8 @@ object Fido2ProviderModule {
fido2CredentialManager: Fido2CredentialManager,
dispatcherManager: DispatcherManager,
intentManager: IntentManager,
biometricsEncryptionManager: BiometricsEncryptionManager,
featureFlagManager: FeatureFlagManager,
clock: Clock,
): Fido2ProviderProcessor =
Fido2ProviderProcessorImpl(
@@ -54,6 +58,8 @@ object Fido2ProviderModule {
fido2CredentialManager,
intentManager,
clock,
biometricsEncryptionManager,
featureFlagManager,
dispatcherManager,
)

View File

@@ -6,6 +6,7 @@ import com.bitwarden.fido.Origin
import com.bitwarden.fido.UnverifiedAssetLink
import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.vault.CipherView
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
@@ -22,10 +23,11 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2Cred
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
import timber.log.Timber
/**
* Primary implementation of [Fido2CredentialManager].
@@ -48,41 +50,45 @@ class Fido2CredentialManagerImpl(
fido2CreateCredentialRequest: Fido2CreateCredentialRequest,
selectedCipherView: CipherView,
): Fido2RegisterCredentialResult {
val clientData = if (fido2CreateCredentialRequest.callingAppInfo.isOriginPopulated()) {
fido2CreateCredentialRequest
.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 = fido2CreateCredentialRequest
.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 = fido2CreateCredentialRequest
.origin
?: getOriginUrlFromAttestationOptionsOrNull(fido2CreateCredentialRequest.requestJson)
?: return Fido2RegisterCredentialResult.Error
val origin = Origin.Android(
UnverifiedAssetLink(
packageName = fido2CreateCredentialRequest.packageName,
sha256CertFingerprint = fido2CreateCredentialRequest
.callingAppInfo
.getSignatureFingerprintAsHexString()
?: return Fido2RegisterCredentialResult.Error,
host = assetLinkUrl.toHostOrPathOrNull()
?: return Fido2RegisterCredentialResult.Error,
assetLinkUrl = assetLinkUrl,
),
)
return vaultSdkSource
.registerFido2Credential(
request = RegisterFido2CredentialRequest(
userId = userId,
origin = origin,
origin = sdkOrigin,
requestJson = """{"publicKey": ${fido2CreateCredentialRequest.requestJson}}""",
clientData = clientData,
selectedCipherView = selectedCipherView,
@@ -96,7 +102,11 @@ 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(),
)
},
)
}
@@ -115,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
}
@@ -126,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,
@@ -140,22 +155,44 @@ 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())
}
is Fido2ValidateOriginResult.Success -> {
@@ -163,16 +200,7 @@ class Fido2CredentialManagerImpl(
.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,
@@ -184,7 +212,13 @@ class Fido2CredentialManagerImpl(
.mapCatching { json.encodeToString(it) }
.fold(
onSuccess = { Fido2CredentialAssertionResult.Success(it) },
onFailure = { Fido2CredentialAssertionResult.Error },
onFailure = {
Timber.e(it, "Failed to authenticate FIDO2 credential.")
Fido2CredentialAssertionResult.Error(
R.string.passkey_authentication_failed_due_to_an_internal_error
.asText(),
)
},
)
}
}
@@ -196,13 +230,13 @@ class Fido2CredentialManagerImpl(
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

@@ -20,13 +20,4 @@ interface Fido2OriginManager {
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
): Fido2ValidateOriginResult
/**
* Returns the privileged app origin, or null if the calling app is not allowed.
*
* @param callingAppInfo The calling app info.
*
* @return The privileged app origin, or null.
*/
suspend fun getPrivilegedAppOriginOrNull(callingAppInfo: CallingAppInfo): String?
}

View File

@@ -32,13 +32,6 @@ class Fido2OriginManagerImpl(
}
}
override suspend fun getPrivilegedAppOriginOrNull(callingAppInfo: CallingAppInfo): String? {
if (!callingAppInfo.isOriginPopulated()) return null
return callingAppInfo.getOrigin(getGoogleAllowListOrNull().orEmpty())
?: callingAppInfo.getOrigin(getCommunityAllowListOrNull().orEmpty())
?.takeUnless { !callingAppInfo.isOriginPopulated() }
}
private suspend fun validateCallingApplicationAssetLinks(
callingAppInfo: CallingAppInfo,
relyingPartyId: String,
@@ -123,7 +116,10 @@ class Fido2OriginManagerImpl(
}
.fold(
onSuccess = { it },
onFailure = { Fido2ValidateOriginResult.Error.Unknown },
onFailure = {
Timber.e(it, "Failed to validate privileged app: ${callingAppInfo.packageName}")
Fido2ValidateOriginResult.Error.Unknown
},
)
/**
@@ -138,7 +134,6 @@ class Fido2OriginManagerImpl(
target.packageName == rpPackageName &&
statement.relation.containsAll(
listOf(
"delegate_permission/common.get_login_creds",
"delegate_permission/common.handle_all_urls",
),
)
@@ -157,16 +152,4 @@ class Fido2OriginManagerImpl(
?: false
}
.takeUnless { it.isEmpty() }
private suspend fun getGoogleAllowListOrNull(): String? =
assetManager
.readAsset(GOOGLE_ALLOW_LIST_FILE_NAME)
.onFailure { Timber.e(it, "Failed to read Google allow list.") }
.getOrNull()
private suspend fun getCommunityAllowListOrNull(): String? =
assetManager
.readAsset(COMMUNITY_ALLOW_LIST_FILE_NAME)
.onFailure { Timber.e(it, "Failed to read Community allow list.") }
.getOrNull()
}

View File

@@ -20,6 +20,7 @@ data class Fido2CreateCredentialRequest(
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

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

View File

@@ -6,6 +6,8 @@ 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
@@ -22,6 +24,7 @@ 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
@@ -34,9 +37,13 @@ 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
@@ -45,6 +52,7 @@ 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"
@@ -54,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,
@@ -64,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 {
@@ -94,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,
@@ -202,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,
@@ -261,36 +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,
)
.setIcon(
Icon
.createWithResource(
context,
R.drawable.ic_bw_passkey,
),
Icon.createWithResource(
context,
R.drawable.ic_bw_passkey,
),
)
.build()
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

@@ -18,7 +18,7 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_USER_ID
* Checks if this [Intent] contains a [Fido2CreateCredentialRequest] related to an ongoing FIDO 2
* credential creation process.
*/
fun Intent.getFido2CredentialRequestOrNull(): Fido2CreateCredentialRequest? {
fun Intent.getFido2CreateCredentialRequestOrNull(): Fido2CreateCredentialRequest? {
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
val systemRequest = PendingIntentHandler
@@ -39,6 +39,7 @@ fun Intent.getFido2CredentialRequestOrNull(): Fido2CreateCredentialRequest? {
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,7 +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.vault.feature.vault.util.getOrganizationPremiumStatusMap
import com.x8bit.bitwarden.ui.platform.base.util.asText
import java.time.Clock
/**
@@ -25,15 +25,8 @@ class AutofillTotpManagerImpl(
) : AutofillTotpManager {
override suspend fun tryCopyTotpToClipboard(cipherView: CipherView) {
if (settingsRepository.isAutoCopyTotpDisabled) return
val organizationPremiumStatusMap = authRepository
.userStateFlow
.value
?.activeAccount
?.getOrganizationPremiumStatusMap()
.orEmpty()
val isPremium = authRepository.userStateFlow.value?.activeAccount?.isPremium == true
val premiumStatus = organizationPremiumStatusMap[cipherView.organizationId] ?: isPremium
if (!premiumStatus && !cipherView.organizationUseTotp) return
if (!isPremium && !cipherView.organizationUseTotp) return
val totpCode = cipherView.login?.totp ?: return
val totpResult = vaultRepository.generateTotp(
@@ -42,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

@@ -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.
*/
@@ -356,4 +362,44 @@ interface SettingsDiskSource {
* 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?
}

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import android.content.SharedPreferences
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.data.platform.repository.util.bufferedMutableSharedFlow
@@ -10,7 +11,6 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
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
@@ -40,6 +40,9 @@ private const val IS_VAULT_REGISTERED_FOR_EXPORT = "isVaultRegisteredForExport"
private const val ADD_ACTION_COUNT = "addActionCount"
private const val COPY_ACTION_COUNT = "copyActionCount"
private const val CREATE_ACTION_COUNT = "createActionCount"
private const val SHOULD_SHOW_ADD_LOGIN_COACH_MARK = "shouldShowAddLoginCoachMark"
private const val SHOULD_SHOW_GENERATOR_COACH_MARK = "shouldShowGeneratorCoachMark"
private const val RESUME_SCREEN = "resumeScreen"
/**
* Primary implementation of [SettingsDiskSource].
@@ -50,6 +53,7 @@ class SettingsDiskSourceImpl(
private val json: Json,
) : BaseDiskSource(sharedPreferences = sharedPreferences),
SettingsDiskSource {
private val mutableAppLanguageFlow = bufferedMutableSharedFlow<AppLanguage?>(replay = 1)
private val mutableAppThemeFlow = bufferedMutableSharedFlow<AppTheme>(replay = 1)
private val mutableLastSyncFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>()
@@ -78,6 +82,10 @@ class SettingsDiskSourceImpl(
private val mutableHasUserLoggedInOrCreatedAccountFlow = bufferedMutableSharedFlow<Boolean?>()
private val mutableHasSeenAddLoginCoachMarkFlow = bufferedMutableSharedFlow<Boolean?>()
private val mutableHasSeenGeneratorCoachMarkFlow = bufferedMutableSharedFlow<Boolean?>()
private val mutableScreenCaptureAllowedFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
@@ -94,8 +102,12 @@ class SettingsDiskSourceImpl(
key = APP_LANGUAGE_KEY,
value = value?.localeName,
)
mutableAppLanguageFlow.tryEmit(value)
}
override val appLanguageFlow: Flow<AppLanguage?>
get() = mutableAppLanguageFlow.onSubscription { emit(appLanguage) }
override var initialAutofillDialogShown: Boolean?
get() = getBoolean(key = INITIAL_AUTOFILL_DIALOG_SHOWN)
set(value) {
@@ -175,12 +187,15 @@ class SettingsDiskSourceImpl(
storeClearClipboardFrequencySeconds(userId = userId, frequency = null)
removeWithPrefix(prefix = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY.appendIdentifier(userId))
storeVaultRegisteredForExport(userId = userId, isRegistered = null)
storeAppResumeScreen(userId = userId, screenData = null)
// The following are intentionally not cleared so they can be
// restored after logging out and back in:
// - screen capture allowed
// - show autofill setting badge
// - show unlock setting badge
// - should show add login coach mark
// - should show generator coach mark
}
override fun getAccountBiometricIntegrityValidity(
@@ -482,6 +497,48 @@ class SettingsDiskSourceImpl(
)
}
override fun getShouldShowAddLoginCoachMark(): Boolean? =
getBoolean(key = SHOULD_SHOW_ADD_LOGIN_COACH_MARK)
override fun storeShouldShowAddLoginCoachMark(shouldShow: Boolean?) {
putBoolean(
key = SHOULD_SHOW_ADD_LOGIN_COACH_MARK,
value = shouldShow,
)
mutableHasSeenAddLoginCoachMarkFlow.tryEmit(shouldShow)
}
override fun getShouldShowAddLoginCoachMarkFlow(): Flow<Boolean?> =
mutableHasSeenAddLoginCoachMarkFlow.onSubscription {
emit(getBoolean(key = SHOULD_SHOW_ADD_LOGIN_COACH_MARK))
}
override fun getShouldShowGeneratorCoachMark(): Boolean? =
getBoolean(key = SHOULD_SHOW_GENERATOR_COACH_MARK)
override fun storeShouldShowGeneratorCoachMark(shouldShow: Boolean?) {
putBoolean(
key = SHOULD_SHOW_GENERATOR_COACH_MARK,
value = shouldShow,
)
mutableHasSeenGeneratorCoachMarkFlow.tryEmit(shouldShow)
}
override fun getShouldShowGeneratorCoachMarkFlow(): Flow<Boolean?> =
mutableHasSeenGeneratorCoachMarkFlow.onSubscription {
emit(getShouldShowGeneratorCoachMark())
}
override fun storeAppResumeScreen(userId: String, screenData: AppResumeScreenData?) {
putString(
key = RESUME_SCREEN.appendIdentifier(userId),
value = screenData?.let { json.encodeToString(it) },
)
}
override fun getAppResumeScreen(userId: String): AppResumeScreenData? =
getString(RESUME_SCREEN.appendIdentifier(userId))?.let { json.decodeFromStringOrNull(it) }
private fun getMutableLastSyncFlow(
userId: String,
): MutableSharedFlow<Instant?> =

View File

@@ -0,0 +1,36 @@
package com.x8bit.bitwarden.data.platform.datasource.disk.model
import java.security.PrivateKey
import java.security.cert.X509Certificate
/**
* Represents a mutual TLS certificate.
*/
data class MutualTlsCertificate(
val alias: String,
val privateKey: PrivateKey,
val certificateChain: List<X509Certificate>,
) {
/**
* Leaf certificate of the chain.
*/
val leafCertificate: X509Certificate?
get() = certificateChain.lastOrNull()
/**
* Root certificate of the chain.
*/
val rootCertificate: X509Certificate?
get() = certificateChain.firstOrNull()
override fun toString(): String = leafCertificate
?.let {
buildString {
appendLine("Subject: ${it.subjectDN}")
appendLine("Issuer: ${it.issuerDN}")
appendLine("Valid From: ${it.notBefore}")
appendLine("Valid Until: ${it.notAfter}")
}
}
?: ""
}

View File

@@ -0,0 +1,16 @@
package com.x8bit.bitwarden.data.platform.datasource.disk.model
/**
* Location of the key data.
*/
enum class MutualTlsKeyHost {
/**
* Key is stored in the system key chain.
*/
KEY_CHAIN,
/**
* Key is stored in a private instance of the Android Key Store.
*/
ANDROID_KEY_STORE,
}

View File

@@ -14,6 +14,10 @@ import com.x8bit.bitwarden.data.platform.datasource.network.service.EventService
import com.x8bit.bitwarden.data.platform.datasource.network.service.EventServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManager
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManagerImpl
import com.x8bit.bitwarden.data.platform.manager.KeyManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -70,6 +74,17 @@ object PlatformNetworkModule {
@Singleton
fun providesRefreshAuthenticator(): RefreshAuthenticator = RefreshAuthenticator()
@Provides
@Singleton
fun provideSslManager(
keyManager: KeyManager,
environmentRepository: EnvironmentRepository,
): SslManager =
SslManagerImpl(
keyManager = keyManager,
environmentRepository = environmentRepository,
)
@Provides
@Singleton
fun provideRetrofits(
@@ -77,6 +92,7 @@ object PlatformNetworkModule {
baseUrlInterceptors: BaseUrlInterceptors,
headersInterceptor: HeadersInterceptor,
refreshAuthenticator: RefreshAuthenticator,
sslManager: SslManager,
json: Json,
): Retrofits =
RetrofitsImpl(
@@ -84,6 +100,7 @@ object PlatformNetworkModule {
baseUrlInterceptors = baseUrlInterceptors,
headersInterceptor = headersInterceptor,
refreshAuthenticator = refreshAuthenticator,
sslManager = sslManager,
json = json,
)

View File

@@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthToke
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManager
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
@@ -14,6 +15,9 @@ import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
import timber.log.Timber
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
/**
* Primary implementation of [Retrofits].
@@ -24,6 +28,7 @@ class RetrofitsImpl(
headersInterceptor: HeadersInterceptor,
refreshAuthenticator: RefreshAuthenticator,
json: Json,
private val sslManager: SslManager,
) : Retrofits {
//region Authenticated Retrofits
@@ -67,6 +72,10 @@ class RetrofitsImpl(
baseClient
.newBuilder()
.addInterceptor(loggingInterceptor)
.setSslSocketFactory(
sslContext = sslManager.sslContext,
trustManagers = sslManager.trustManagers,
)
.build(),
)
.build()
@@ -93,6 +102,10 @@ class RetrofitsImpl(
.newBuilder()
.authenticator(refreshAuthenticator)
.addInterceptor(authTokenInterceptor)
.setSslSocketFactory(
sslContext = sslManager.sslContext,
trustManagers = sslManager.trustManagers,
)
.build()
}
@@ -133,9 +146,22 @@ class RetrofitsImpl(
.newBuilder()
.addInterceptor(baseUrlInterceptor)
.addInterceptor(loggingInterceptor)
.setSslSocketFactory(
sslContext = sslManager.sslContext,
trustManagers = sslManager.trustManagers,
)
.build(),
)
.build()
private fun OkHttpClient.Builder.setSslSocketFactory(
sslContext: SSLContext,
trustManagers: Array<TrustManager>,
): OkHttpClient.Builder =
sslSocketFactory(
sslContext.socketFactory,
trustManagers.first() as X509TrustManager,
)
//endregion Helper properties and functions
}

View File

@@ -15,6 +15,7 @@ import kotlinx.serialization.encoding.Encoder
*/
@Suppress("UnnecessaryAbstractClass")
abstract class BaseEnumeratedIntSerializer<T : Enum<T>>(
private val className: String,
private val values: Array<T>,
private val default: T? = null,
) : KSerializer<T> {
@@ -29,7 +30,7 @@ abstract class BaseEnumeratedIntSerializer<T : Enum<T>>(
val decodedValue = decoder.decodeInt().toString()
return values.firstOrNull { it.serialNameAnnotation?.value == decodedValue }
?: default
?: throw IllegalArgumentException("Unknown value $decodedValue")
?: throw IllegalArgumentException("Unknown value $decodedValue for $className")
}
override fun serialize(encoder: Encoder, value: T) {

View File

@@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.platform.datasource.network.ssl
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
/**
* Interface for managing SSL connections.
*/
interface SslManager {
/**
* The SSL context to use for SSL connections.
*/
val sslContext: SSLContext
/**
* The trust managers to use for SSL connections.
*/
val trustManagers: Array<TrustManager>
}

View File

@@ -0,0 +1,116 @@
package com.x8bit.bitwarden.data.platform.datasource.network.ssl
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.core.net.toUri
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsCertificate
import com.x8bit.bitwarden.data.platform.datasource.disk.model.MutualTlsKeyHost
import com.x8bit.bitwarden.data.platform.manager.KeyManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import java.net.Socket
import java.security.KeyStore
import java.security.Principal
import java.security.PrivateKey
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedKeyManager
/**
* Primary implementation of [SslManager].
*/
class SslManagerImpl(
private val keyManager: KeyManager,
private val environmentRepository: EnvironmentRepository,
) : SslManager {
/*
This property must only be accessed from a background thread. Accessing this property from
the main thread will result in an exception being thrown when retrieving the mutual TLS
certificate from [KeyManager].
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@get:WorkerThread
internal val mutualTlsCertificate: MutualTlsCertificate?
get() {
val keyUri = getKeyUri()
?: return null
val host = MutualTlsKeyHost
.entries
.find { it.name == keyUri.authority }
?: return null
val alias = keyUri.path
?.trim('/')
?.takeUnless { it.isEmpty() }
?: return null
return keyManager.getMutualTlsCertificateChain(
alias = alias,
host = host,
)
}
override val trustManagers: Array<TrustManager>
get() = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
.apply { init(null as KeyStore?) }
.trustManagers
override val sslContext: SSLContext
get() = SSLContext
.getInstance("TLS")
.apply {
init(
arrayOf(X509ExtendedKeyManagerImpl()),
trustManagers,
null,
)
}
private fun getKeyUri(): Uri? = environmentRepository
.environment
.environmentUrlData
.keyUri
?.toUri()
private inner class X509ExtendedKeyManagerImpl : X509ExtendedKeyManager() {
override fun chooseClientAlias(
keyType: Array<out String>?,
issuers: Array<out Principal>?,
socket: Socket?,
): String = mutualTlsCertificate?.alias ?: ""
override fun getCertificateChain(
alias: String?,
): Array<X509Certificate>? =
mutualTlsCertificate
?.certificateChain
?.toTypedArray()
override fun getPrivateKey(alias: String?): PrivateKey? =
mutualTlsCertificate
?.privateKey
//region Unused server side methods
override fun getServerAliases(
alias: String?,
issuers: Array<out Principal>?,
): Array<String> = arrayOf()
override fun getClientAliases(
keyType: String?,
issuers: Array<out Principal>?,
): Array<String> = emptyArray()
override fun chooseServerAlias(
alias: String?,
issuers: Array<out Principal>?,
socket: Socket?,
): String = ""
//endregion Unused server side methods
}
}

View File

@@ -17,9 +17,12 @@ import retrofit2.HttpException
* will be attempted to be parsed.
* @param json [Json] serializer to use.
*/
inline fun <reified T> BitwardenError.parseErrorBodyOrNull(codes: List<Int>, json: Json): T? =
inline fun <reified T> BitwardenError.parseErrorBodyOrNull(
codes: List<NetworkErrorCode>,
json: Json,
): T? =
(this as? BitwardenError.Http)
?.takeIf { codes.any { it == this.code } }
?.takeIf { codes.any { it.code == this.code } }
?.responseBodyString
?.let { responseBody ->
json.decodeFromStringOrNull(responseBody)
@@ -28,5 +31,7 @@ inline fun <reified T> BitwardenError.parseErrorBodyOrNull(codes: List<Int>, jso
/**
* Helper for calling [parseErrorBodyOrNull] with a single code.
*/
inline fun <reified T> BitwardenError.parseErrorBodyOrNull(code: Int, json: Json): T? =
parseErrorBodyOrNull(listOf(code), json)
inline fun <reified T> BitwardenError.parseErrorBodyOrNull(
code: NetworkErrorCode,
json: Json,
): T? = parseErrorBodyOrNull(codes = listOf(code), json = json)

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