1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

359 Commits

Author SHA1 Message Date
github-actions[bot]
6c2711f576 Bump version to 2023.2.0 (#2374)
* [SG-1057] Make check breaches true by default (#2351)

(cherry picked from commit 6875389948)

* [SG-1055] Init strength bar color (#2352)

(cherry picked from commit 3780587991)

* [SG-1052] Fix copy (#2353)

(cherry picked from commit 0bd1b3f45f)

* [SG-1056] Fix login prompt loop for added accounts (#2357)

* SG-1056 Fix login prompt loop for added accounts

* SG-1056 Fix PR comment

(cherry picked from commit f42c677d5a)

* Bumped version to 2023.2.0

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: Carlos Gonçalves <cgoncalves@bitwarden.com>
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
Co-authored-by: Opeyemi Alao <54288773+Eeebru@users.noreply.github.com>
(cherry picked from commit bc439b45c9)
2023-02-15 23:25:57 +00:00
Carlos Gonçalves
bd5b7d1639 [SG-1056] Fix login prompt loop for added accounts (#2357)
* SG-1056 Fix login prompt loop for added accounts

* SG-1056 Fix PR comment

(cherry picked from commit f42c677d5a)
2023-02-10 13:02:16 -05:00
André Bispo
f5e6343870 [SG-1052] Fix copy (#2353)
(cherry picked from commit 0bd1b3f45f)
2023-02-09 15:14:25 -05:00
André Bispo
9d174a1688 [SG-1055] Init strength bar color (#2352)
(cherry picked from commit 3780587991)
2023-02-09 15:13:11 -05:00
André Bispo
00a1e11ff4 [SG-1057] Make check breaches true by default (#2351)
(cherry picked from commit 6875389948)
2023-02-09 15:11:12 -05:00
github-actions[bot]
490b74dd26 Autosync the updated translations (#2346)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-06 13:36:22 +01:00
github-actions[bot]
66d05e1b00 Autosync the updated translations (#2345)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-06 11:57:22 +01:00
André Bispo
3d3101c3ab [SG-1035] Fix label for log in with device (#2338) 2023-02-03 19:19:48 +00:00
github-actions[bot]
ccde4270d0 Autosync the updated translations (#2342)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-03 14:18:33 +01:00
github-actions[bot]
a1f799302e Autosync the updated translations (#2340)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-03 14:08:03 +01:00
André Bispo
4619e257e8 [SG-995] Remove automatic navigation from Home to Login if remembered (#2335) 2023-02-03 09:53:05 +00:00
Kyle Spearrin
c54a14cd3f set all kdf params on account profile (#2339) 2023-02-02 18:00:29 -05:00
aj-rosado
a96d95c95b [PS-2330][PS-2339][PS-2332][PS-2333] Checking for username terms when adding a password, if they exist don't add. Adding fields that contain username terms to the usernamefields collection (#2331) 2023-02-01 17:41:14 +00:00
André Bispo
e61ca489ce [SG-834] Mobile pending login requests management screen (#2281)
* Bootstrap new classes for settings list

* [SG-834] Add new method GetActivePasswordlessLoginRequestsAsync to AuthService

* [SG-834] Add generic handle exception method to BaseViewModel

* [SG-834] Add request verification to settings entry

* [SG-834] Add text resources

* [SG-834] Update view and viewmodel

* [SG-834] Remove unnecessary property assignment

* [SG-834] removed logger resolve
2023-02-01 12:22:17 +00:00
Bernd Schoolmann
c3ad5f0580 [PS-2358] Add kdf configuration options (#2328)
* Implement kdf configuration

* Remove unused import

* Move kdf parameters to kdfConfiguration struct

* Remove unused state migration service keys

* Revert newline changes in PCLCryptoFunctionService

* Update KdfConfiguration.cs

* Add checks for argon2, clean statemigration service

* Update constants

* Clean up code

* Further cleanup

* Change KdfType to non-nullable in SetKeyConnectorKeyRequest

---------

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2023-01-30 11:34:50 -05:00
Matt Bishop
8b08f906bd Remove fallout from test run (#2332) 2023-01-27 11:27:25 -05:00
github-actions[bot]
4ad5f5ae37 Autosync the updated translations (#2330)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-27 01:40:53 +01:00
André Bispo
68a6449339 [SG-516] Additional forwarded email providers for username generator - mobile (#2304)
* [SG-516] Added DuckDuckGo provider

* [SG-516] Add Fastmail as generator provider

* [SG-516] code clean up

* [SG-516] Default to service empty if first time on screen. Order services by alphabetic order.

* [SG-516] Removed unnecessary prop.

* [PS-2278] Fixed inverted eye bug.

* [SG-516] Add icon glyph converter

* [SG-516] Fixed enum default value and ordering
2023-01-26 13:53:48 +00:00
Kyle Spearrin
b8d53b0f81 Argon2id KDF (#2309)
* add gcc_flags for libargon2.a

* fix up ios proj

* remove unused tag

* add gcc_flags to ios projects

* ios libargon2 binary

* fix paths in ios project

* update pathing on other projs

* Argon2id primitive

* fix typing issues

* comment

* remove ds store

* [PS-2249] Implement Argon2 (#2293)

* Implement Argon2

* Fix incorrect argon2 type on iOS

* Switch argon2 implementation to native bindings

* Change argon2 to save iterations instead of memory as 'kdfIterations'

* Remove mistakenly added import

* Remove unused library

* cleanup

* move android libs

* move android libs

* Revert "move android libs"

This reverts commit 0b91b22cd2.

* Revert "move android libs"

This reverts commit 139839c469.

* PR feedback

Co-authored-by: Bernd Schoolmann <mail@quexten.com>
2023-01-25 07:58:36 -05:00
aj-rosado
dbfd15b819 [PS-2275] Update the default kdf iterations to 600k. (#2305)
* [PS-2275] Update the default kdf iterations to 350k.

* update to 600k per latest owasp
2023-01-24 20:08:52 +00:00
Daniel James Smith
acd0cb119d [PS-2324] Add honorific title mx (#2313) 2023-01-23 16:28:04 +01:00
André Bispo
d61bc4b5c1 [SG-460] Master Password security checks (mobile) (#2312)
* [SG-886] MasterPassword Strength Indicator (#2238)

* [SG-886] Add password strength indicator control

* [SG-570] Add weak password dialog check

* [SG-886] rename enum password strength

* [SG-886] Change control scale

* [SG-886] Move calculate user inputs to IPasswordGenerationService, refactor.

* [SG-886] Move formatted string to xaml. Move minimum chars to constant

* [SG-886] String to enum converter

* [SG-886] PR fixes. Code refactor control

* [SG-886] Update UI on OS theme change.

* [SG-886] Move colors to view

* [SG-886] Fixed password strength validation

* [SG-564][SG-565] Check Exposed Password (#2239)

* [SG-886] Add password strength indicator control

* [SG-570] Add weak password dialog check

* [SG-886] rename enum password strength

* [SG-564] [SG-565] Add check for exposed password and show dialog

* code format

* [SG-886] Change control scale

* [SG-886] Move calculate user inputs to IPasswordGenerationService, refactor.

* [SG-886] Move formatted string to xaml. Move minimum chars to constant

* [SG-886] String to enum converter

* [SG-886] Remove import

* [SG-886] Update UI on OS theme change.

* [SG-886] Move colors to view

* [SG-886] Fixed password strength validation
2023-01-20 13:38:31 +00:00
github-actions[bot]
5aa1146657 Autosync the updated translations (#2310)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-20 12:54:47 +01:00
André Bispo
f15fd246a8 Passwordless login 2FA not working (#2289)
* [SG-174] Fix 2FA passwordless login

* [SG-982] Enable login with device
2023-01-16 18:25:44 +00:00
Michał Chęciński
2b8547878a Fix autobump workflow (#2298) 2023-01-16 18:45:23 +01:00
github-actions[bot]
942d5d29d2 Bumped version to 2023.1.1 (#2297)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-16 10:29:15 +01:00
github-actions[bot]
fde63a836d Autosync the updated translations (#2292)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-13 10:03:24 +01:00
Brandon Maharaj
6102a0c115 [SG-912] Modify the mobile app to retrieve the user's avatar color (#2284)
* [SG-912] Modify the mobile app to retrieve the user's avatar color (#2277)

* work: baseline

* fix: dont use profile for store

* fiix: use userid in key

* fix: lookup on AccountView list create

* fix my own bad advice + tweaks

* Autosync the updated translations (#2279)

* fix my own bad advice + tweaks

* fiix: use userid in key

* [PS-1352] Fix ignore diacritics in search (#2044)

* Fix ignore diacritics in search

This change updates the search function to ignore diacritical marks in search results. Marks are stripped from both the search input and results.

* Removed logs, added null or whitespace validation and improved formatting


* [PS-2145] add rainsee browser series support (#2272)

* fix: lookup on AccountView list create

* Autosync the updated translations (#2279)

* fix my own bad advice + tweaks

* fix: single state grab is cool
2023-01-12 13:27:10 -05:00
Federico Maccaroni
4f4953206e [EC-469] Improve ApiException message (#2288)
* EC-469 Improve ApiException message to have the validation errors and message provided by the ErrorResponse

* EC-469 Updated default message format for ErrorResponse GetFullMessage()
2023-01-12 10:31:27 -03:00
github-actions[bot]
f772ee7068 Bumped version to 2023.1.0 (#2287)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-11 09:55:57 +01:00
Matt Bishop
8f93e6bf5f Upload and process test results as an artifact and report (#2286) 2023-01-10 13:48:22 -05:00
André Bispo
64fefac194 [SG-978] Added queries to http and https into manifest (#2282) 2023-01-06 13:46:03 -05:00
github-actions[bot]
d784b1290b Autosync the updated translations (#2279)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-06 01:23:41 +01:00
Auj625197595
0f2bc2fa25 [PS-2145] add rainsee browser series support (#2272) 2023-01-05 17:23:09 +00:00
noncenz
0e856d2add [PS-1352] Fix ignore diacritics in search (#2044)
* Fix ignore diacritics in search

This change updates the search function to ignore diacritical marks in search results. Marks are stripped from both the search input and results.

* Removed logs, added null or whitespace validation and improved formatting

Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>
Co-authored-by: Andre Rosado <arosado@bitwarden.com>
2023-01-05 16:34:06 +00:00
mp-bw
acc587ce45 Fix repo url for F-Droid 1.15+ (#2276) 2023-01-04 14:28:47 -03:00
Daniel James Smith
66180397d9 Add needs-qa label to BANNED_LABELS (#2274) 2023-01-03 20:09:13 +01:00
Federico Maccaroni
571c4b8d22 EC-819 Update iOS app icon (#2270) 2022-12-30 17:52:25 +00:00
Federico Maccaroni
660ba3d722 [EC-628] Added Crashlytics to the watchOS project (#2267)
* EC-628 Added Crashlytics to the watchOS project, missing GoogleService-Info.plist for now

* addition of GoogleServices-info.plist.gpg

* Re-add GoogleService-info.plist.gpg

* EC-628 Updated build.yml to decrypt and copy the GoogleService-Info.plist for Crashlytics and also added step to upload Watch dSYMs to Crashlytics

* EC-628 Fix run command upload watch dsym build.yml

* EC-628 Updated Apple iOS macos version to 12 in order to resolve Watch XCode dependencies correctly with SPM

* EC-628 Added some logs to see where I'm located cause it's issuing a wrong path. Also deactivated droid builds so that it's faster to test and doesn't consume resources on that till the build is OK

* EC-628 fixed variable reference build.yml

* EC-628 Removed ls of watch dsym export path before creating the folder that was causing the build to fail

* EC-628 Removed XCode build phase step to auto-upload dsym to firebase and added some logs to find the upload-symbols tool from SPM. Also fix making the dir for the watchOS dSYMs export path

* EC-628 Changed approach to upload watch dSYMs to Firebase by finding the upload-symbols script dynamically with find and then executing it

* EC-628 Added missing ; to command to upload watch dSYMs

* EC-628 Fix buld.yml Watch dSYMs copy to export path

* EC-628 Cleaned build.yml for the watch dSYMs upload to Firebase and bring back droid builds

Co-authored-by: sneakernuts <671942+sneakernuts@users.noreply.github.com>
2022-12-30 17:51:35 +00:00
Kevinlinpr
414cb9bd7e [PS-2116] feat(Support): Add Lemur Browser (#2262)
*  feat(Support): Add Lemur Browser

Lemur Browser is the mobile browser support Chrome and Edge extensions, build from chromium.

🔗 issue - https://github.com/bitwarden/mobile/issues/2260
🔗 Lemur Browser - https://www.lemurbrowser.com/

* 🐞 fix(Sorted alphabetically): Move up one line

Please move this up one line to ensure the sections are sorted alphabetically

6d5cb73445
2022-12-30 13:38:05 +01:00
github-actions[bot]
e588efd0a1 Autosync the updated translations (#2271)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-30 01:19:00 +01:00
Federico Maccaroni
1cdba5f73d EC-868 clear console logging (#2265) 2022-12-29 15:00:13 -03:00
Dan Alcantara
cbccd10271 Add support for the Neeva app (#2045) 2022-12-28 13:19:16 +01:00
Şahin BEZENG
6de6b19944 Add support for Dezor Browser (#2142) 2022-12-28 13:08:40 +01:00
Daniel James Smith
5493b00957 Fix link to contributing docs (#2266) 2022-12-26 17:38:55 +01:00
Federico Maccaroni
81988a7fe7 EC-820 Added icons for Watch Complication and updated the Watch app icons (#2258) 2022-12-23 22:39:50 +00:00
André Bispo
b91ff09e27 [SG-174] Remove login with device button (#2259) 2022-12-23 17:11:14 +00:00
github-actions[bot]
945627d649 Autosync the updated translations (#2257)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-23 01:53:00 +01:00
Federico Maccaroni
307c71ee07 EC-847 improved watch list ui performance by removing the animations on the search box and going back to the simple List instead of the TrackableWithHeaderListView (#2256) 2022-12-22 19:06:21 +00:00
Federico Maccaroni
e72932cbaa [EC-835] Add kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly on Watch Keychain (#2250)
* EC-835 Added in the Watch app keychain accessible when passcode set this device only and when the passcode is set to signal the iPhone to trigger a sync on opening the watch app

* EC-835 Embed LocalAuthentication framework into the watch app to fix no such module when importing it

* EC-835 Changed approach to check if Watch has passcode enabled by using Keychain accessible kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly instead of LAContext

* EC-835 Fix weird error saying unassigned local variable on the CI compiler. It seems it doesn't realize of the full condition
2022-12-22 17:59:12 +00:00
mp-bw
4347c2f81d Android: Show auto-totp-copy toast on pre-13 devices (#2249) 2022-12-19 14:03:22 -03:00
github-actions[bot]
619acfe0fe Autosync the updated translations (#2247)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-16 18:30:52 +01:00
André Bispo
05765c2af9 Remove unused resource entry SelectAddTotpToStoreTheKeySafely (#2248) 2022-12-16 16:26:59 +01:00
Federico Maccaroni
728182de6c [EC-844] Improve Apple Watch states (#2246)
* EC-844 improve need login / need setup states on the watch

* EC-844 Fix naming of things and moved constant to the proper place to maintain format. Also removed UpdateLastUserShouldConnectToWatchAsync from the interface of the StateService given that it's not used outside the service and made it private
2022-12-15 15:21:29 -03:00
Federico Maccaroni
a19b5c4e05 EC-842 Delete items that are not included in the sync to the watch (#2242) 2022-12-15 13:03:34 -03:00
Federico Maccaroni
f6895a0733 EC-846 Revert build.yml Android was not being built because a condition added while developing the Apple Watch app in another branch merged to master (#2245) 2022-12-15 10:57:10 -03:00
Federico Maccaroni
3e48b7a968 EC-785 Bumped Apple Watch version to 2022.12.0 (#2243) 2022-12-14 22:03:06 +00:00
Todd Martin
ebf65ecb96 Set push token state values to be user-specific (#2200)
* Changed the current push token and last registration time to be user-based

* Fixed compile error.

* Fixed interface implementation.

* Fixed compile error for Android.

* Refactored to handle getting active user ID within state service

* Refactored methods allow existing logic to handle getting the active user.

* Updated to reconcile options.

* Updated naming and fixed issue with UserId.

* Removed space between constants.
2022-12-14 16:07:04 -05:00
github-actions[bot]
37dab0928b Bumped version to 2022.12.0 (#2241)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-14 21:04:08 +00:00
André Bispo
aa0544cd3d [SG-872] Create generic exception handler for mobile (#2222)
* [SG-872] Add method to handle exception in BaseViewModel

* [SG-872] Code format
2022-12-13 21:53:04 +00:00
André Bispo
f4b4cfc9de [SG-870] Add null check on notification received (#2221) 2022-12-13 21:50:05 +00:00
Federico Maccaroni
7785b2dbf9 EC-841 Fix Watch fetch cipher and added condition for previews (#2236) 2022-12-13 19:36:45 +00:00
Federico Maccaroni
e3dafb502b EC-834 Watch UI/UX fixes (#2234) 2022-12-13 14:50:55 -03:00
Federico Maccaroni
b30fc12135 EC-830 Fix state checking when watch app had already been installed before, so reset it if first run. (#2232) 2022-12-13 11:27:33 -03:00
Federico Maccaroni
28d204f2b1 EC-833 fix WatchDeviceService registration to be called from the extensions appropiately (#2233) 2022-12-13 11:27:15 -03:00
github-actions[bot]
1a3ff5ec41 Autosync the updated translations (#2230)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-09 11:59:54 +01:00
flawedworld
2f6fd476a2 Add support for Vanadium in GrapheneOS (#2199)
Co-authored-by: mp-bw <59324545+mp-bw@users.noreply.github.com>
2022-12-08 10:07:06 -05:00
mp-bw
7a08452fa8 Automatically continue when enter key pressed during email entry (#2227) 2022-12-07 16:51:32 +00:00
Álison Fernandes
fa6bac3b43 EC-395 Apple Watch MVP (#2228)
* [EC-426] Add watchOS PoC app (#2054)

* EC-426 Added watchOS app, configured iOS.csproj to bundle the output of XCode build into the Xamarin iOS app and added some custom logic to use WCSession to communicate between the iOS and the watchOS apps

* EC-426 Removed Info.plist from iOS.Core project given that it's not needed

* [EC-426] Added new encrypted watch app profiles

* EC-426 added configuration for building watchApp and bundle it up on the iOS one

* EC-426 Fix build for watchOS

* EC-426 Fix build for watchOS applied shell bash

* EC-426 Fix build for watchOS echo

* EC-426 Fix build for watchOS simplify

* EC-426 Fix build for watchOS added workspace path

* EC-426 Changed code sign identity of watchOS project to Apple Distribution

* EC-426 added manual code sign style and specified the provisioning profile for the targets on the watch xcode project

* EC-426 updated path to watchOS on release on iOS.csproj and disabled android and f-.droid

* EC-426 fix build

* EC-426 fix path and check listing of directory of watchOS output just in case

* EC-426 Fix Apple Watch build to list the folder recursively just in case we need to change the path for the watch bundle

* EC-426 TEMP Change texts on input on login and lock to show that the app is for the Watch PoC testing

* EC-426 Fix WatchApp build path

* EC-426 Added WatchOS AppIcons

* EC-426 added gitignore for XCode project removed files supposed to be ignored

* EC-426 Cleaned the code a bit to avoid misbehavior

* EC-426 Code cleanup

Co-authored-by: Joseph Flinn <joseph.s.flinn@gmail.com>

* [EC-585] Added data, encryption and some helpers and structure to the Watch app (#2164)

* [EC-585] Added foundation classes on the watch to handle CoreData and some fixes on the communication of the ciphers, also some helper classes to store in keychain and encrypt data

* EC-585 Added keychain helper, encryption helpers and added data storage using CoreData configuring it appropiately. View and ViewModel are here only to test that the fetching/saving works but it's not the actual UI of the watch app. Also removed all the places where the automatic file signature was added by XCode

* EC-585 Fixed CipherServiceMock to implement protocol

* EC-585 Fixed DeviceActionService duplicated services

* [EC-614] Apple Watch MVP Cipher list UI (#2175)

* [EC-585] Added foundation classes on the watch to handle CoreData and some fixes on the communication of the ciphers, also some helper classes to store in keychain and encrypt data

* EC-585 Added keychain helper, encryption helpers and added data storage using CoreData configuring it appropiately. View and ViewModel are here only to test that the fetching/saving works but it's not the actual UI of the watch app. Also removed all the places where the automatic file signature was added by XCode

* EC-585 Fixed CipherServiceMock to implement protocol

* EC-585 Fixed DeviceActionService duplicated services

* EC-614 Implemented watch ciphers list UI

* [EC-615] Apple Watch MVP Cipher details UI (#2192)

* [EC-585] Added foundation classes on the watch to handle CoreData and some fixes on the communication of the ciphers, also some helper classes to store in keychain and encrypt data

* EC-585 Added keychain helper, encryption helpers and added data storage using CoreData configuring it appropiately. View and ViewModel are here only to test that the fetching/saving works but it's not the actual UI of the watch app. Also removed all the places where the automatic file signature was added by XCode

* EC-585 Fixed CipherServiceMock to implement protocol

* EC-585 Fixed DeviceActionService duplicated services

* EC-614 Implemented watch ciphers list UI

* EC-615 Added cipher details UI to watch and also implemented logic and helpers to generate the TOTPs

* EC-615 Added value transformer to login uris on the cipher entity

* EC-617 Added state view on watch app and some state helpers and wired it on the CipherListView. Also added some images (#2195)

* [EC-581] Implement Apple Watch MVP Sync (#2206)

* EC-581 Implemented sync iPhone -> watchOS, fix some issues with the watch database and sync flows for login/locks/multiple accounts

* EC-581 Added watch sync on unlocking and need setup state when no user is synced and the session is not active

* EC-581 Removed unused method

* EC-581 Fix format

* EC-759 Added avatar row on cipher list header to display avatar icon and email (#2213)

* [EC-786] Apple Watch MVP Sync fixes (#2214)

* EC-786 Commented things that are not going to be included on the MVP and fixed issue on the dictionary sent on the applicationContext to have a changing key based on time

* EC-786 Commented need unlock state

* EC-579 Added logic for Connect To Watch on iOS settings and moved it to the correct place. Also improved the synchronization and watch session activation logic (#2218)

* EC-616 Added search header for ciphers and polished the code (#2226)

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Joseph Flinn <joseph.s.flinn@gmail.com>
2022-12-07 11:39:20 -05:00
André Bispo
2a60ff62d8 [SG-601] Enhance experience when user denies camera access in Authenticator (#2205)
* [SG-601] Handle camera denied permissions

* [SG-601] Code format

* [SG-601] PR Fixes

* [SG-601] Change resource text to singular

* [SG-601] Remove horizontal and vertical options

* [SG-601] Add start and stop scanner methods

* [SG-601] Remove parameter from ScanPage

* [SG-601] Move initialization to viewmodel

* [SG-601] Fix zxing scanning bug

* [SG-601] Move RunUIThread inside of method
2022-12-06 16:30:46 +00:00
github-actions[bot]
eaa4f193ce Bumped version to 2022.11.1 (#2224)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-05 13:56:00 -08:00
mp-bw
6973a0b71c [BEEEP] Support for automatic TOTP token copy via external autofill (Android) (#2220)
* Android: Support for automatic TOTP copy via external autofill

* update iOS autofill interface

* additional tweaks
2022-12-05 12:49:34 -05:00
Opeyemi
bafd9ff85d update ::set-output to the latest method (#2217)
* update ::set-output to the latest method

* Update workflow for linter error
2022-12-05 15:04:34 +00:00
aj-rosado
4580033477 [PS-1707] improve dark layout colors (#2209)
* [PS-1707] Changed the callout color to a Theme Color

* [PS-1707] Changed color defaults to defined colors

* [PS-1707] Added an effect to change the switch hint color to a theme color

* [PS-1707] Removed HintColorSwitchEffect and added the content to the CustomSwitchRenderer to affect all switches and update on theme change
2022-12-02 16:38:25 +00:00
github-actions[bot]
a4673021a9 Autosync the updated translations (#2215)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-02 01:28:13 +01:00
github-actions[bot]
8e2e143e6f Bumped version to 2022.11.0 (#2212)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-12-01 16:56:36 +00:00
André Bispo
f43f9171e5 [SG-860] Add condition to execute notification tap code (#2211) 2022-12-01 11:58:27 +00:00
github-actions[bot]
3c8a8862ca Autosync the updated translations (#2204)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-11-25 01:26:09 +01:00
Álison Fernandes
969b89bb4d SG-837 Fixes: QA build crashing while attaching (#2203) 2022-11-24 15:49:12 +00:00
github-actions[bot]
164b8970d3 Autosync the updated translations (#2198)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-11-18 04:39:37 +01:00
André Bispo
34fd30e157 [SG-831] Pull Down Sync does not retrieve pending AuthRequests (#2196)
* [SG-831] Pull to refresh forces refresh.

* [SG-831] Expose sync login request method to be used independently

* [SG-831] Change sync order
2022-11-17 16:02:34 +00:00
André Bispo
8e09f0cc15 [SG-808] Change navigation on remove account after logout by timeout (#2193) 2022-11-16 12:38:45 +00:00
André Bispo
693a4ef776 [SG-816] Get all login requests and pick the most recent (#2191)
* [SG-816] Get all login requests anfd pick the most recent

* [SG-816] Add check if active user has approve login with device active

* [SG-816] Build fix. Fix response model.

* [SG-816] Move code to sync service
2022-11-15 17:36:21 +00:00
André Bispo
0992a989d4 [SG-778] Adjust mobile client to handle previously-responded-to passwordless request (#2190)
* [SG-778] Add properties to response model

* [SG-778] Add validation for request already answered

* [SG-778] Remove unnecessary properties

* [SG-778] Remove unnecessary assignments
2022-11-15 14:17:26 +00:00
Todd Martin
1b137a8a8a Added check to make sure it doesn't set a deployment on a Dry Run. (#2188) 2022-11-14 13:43:45 -05:00
André Bispo
7e8e86a77a [SG-813] Not You? crashes app after vault logout timeout (#2184)
* Merge branch 'master' into feature/SG-174-login-with-device

* [SG-813] Fix merge

* [SG-813] rename HomePage parameter name

* [SG-813] Added NavParams for home page on account switching.

* [SG-813] Remove account showing when adding new account.

* [SG-813] Add account switch pop up if email already exists

* [SG-813] Add default account avatar to HomePage

* [SG-813] Code format

* [SG-813] Remove unused import

* [SG-813] Renamed checkNavigateLogin to shouldCheckRememberEmail

* [SG-813] Move prompt account switch to account manager service

* [SG-813] Remove Account button appears if email is the same as the ActiveUser

* [SG-813] Fix code duplicate

* [SG-813] Fix for android RemoveAccount button

* [SG-813] Code format
2022-11-11 18:14:30 +00:00
Robyn MacCallum
20c1e2d7f2 Add syncCompleted to check to get passwordless requests (#2186) 2022-11-11 12:33:17 -05:00
github-actions[bot]
7870c2706b Autosync the updated translations (#2185)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-11-11 12:34:15 +01:00
Patrick
5a5f50605b Update AppResources.resx (#2181) 2022-11-10 13:44:18 -05:00
Robyn MacCallum
0106732cbd [SG-808] Only show 'remove account' option if user is in Accounts list (#2179)
* Only show 'remove account' option if user is in Accounts list

* Condense existing email logic

* Add line break

* Condense to local variable

* Cleanup spacing
2022-11-10 07:06:17 -05:00
André Bispo
9ae269dd57 [SG-174] Login with Device Request - Mobile (#2167)
* [SG-174] Add new login request services to Api

* [SG-174] Fix typo

* [SG-174] Enable login with device button.

* [SG-174] Add new login request page and viewmodel

* [SG-174] Add new text resources

* [SG-174] Add new RSA Decrypt method with string param

* [SG-174] Change create login request method

* [SG-174] Add new method to auth service to login passwordless

* [SG-174] Refactor login helper method to work with passwordless

* [SG-174] Fix service registration

* [SG-174] Update token request to support passwordless

* [SG-174] Update Api service with passwordless methods

* [SG-174] Fix App csproj references

* [SG-174] Remove unnecessary argument

* [SG-174] dotnet format

* [SG-174] Fixed iOS Extensions

* [SG-174] Change Command to ICommand

* [SG-174] Change Gesture Recognizer to Command

* [SG-174] Fix close action

* [SG-174] Code format

* [SG-174] Fix android frame shadow bug

* [SG-174] PR fixes
2022-11-09 16:25:48 +00:00
Opeyemi
04ed47d545 Devops 1039 update release flow dry run step names (#2177) 2022-11-08 22:44:50 +00:00
Opeyemi
6160535c03 add run-name for release to include workflow trigger (#2174) 2022-11-08 20:27:22 +00:00
André Bispo
8d92373c88 [SG-806] add try catch on initAsync (#2173) 2022-11-07 13:41:02 +00:00
André Bispo
6bfc8f7d49 [SG-802] Add if check on notification tap. (#2172) 2022-11-07 13:40:48 +00:00
André Bispo
69f02eef82 [SG-800] Selecting notification for inactive account on iOS while app is open does not display the request (#2166)
* [SG-800] Remove await from ActionSheet
If the user never answers the pop up, the lock is never released.

* [SG-800] Add try catch
2022-11-04 16:54:16 +00:00
github-actions[bot]
02b8e54988 Autosync the updated translations (#2165)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-11-04 08:11:43 +01:00
Álison Fernandes
99472d2548 [EC-655] Adds build variants to the mobile codebase using a CAKE script (#2161)
* Implemented CAKE build script

* cake script now deals with all of iOS's .plists

* cake now updates iOS bundleid's / Android packagename in codefiles

* iOS Bundle ID / Name should be correctly handled now + refactor

* tabs -> spaces

* Additional code files are now handled by cake

* Additional iOS codefile changes required

* Android's Autofill Label is now changed

* Removed dash from packagenames / bundleIDs

* Fixed CFBundleURLName set

* Added google-services.json to cake preprocessing

* Add CAKE to build workflow
- Android

* Add debug

* Updated cake's GitVersion.Tool

* AndroidManifest manual parsing needs to happen first

* Added Android Constants to build.cake

* [SG-747] Add Android QA build to mobile build pipeline (#2144)

* Add checkout depth

* Build and upload QA artifacts

* Remove missing .aab

* Update build.yml

* Update paths

* Update var names

* Build and upload QA artifacts

* Add in matrix to path.

* Lets not fail all the jobs if something pukes

* Add in some flow logic for QA

* We need strings in pwsh

* Remove extra quotes

* Testing, remove uneeded runs

* Test folder items

* [SG-747] Added more debug info to find problem

* [SG-747] copy signed apk to correct file name for each app variant

* [SG-747] try to fix if statement

* [SG-747] separate decrypt google services into another step with condition.

* [SG-747] fixed typo and line break

* [SG-747] added debug to check output path

* [SG-747] fix package name

* [SG-747] Fixed condition of step execution

* [SG-747] test if cases

* [SG-747] Code clean up

* [SG-747] Added FDroid and iOS steps.

* [SG-747] Removed test step

* [SG-747] Step name changes

* Update condition to be more inclusive

Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>

* [SG-747] Expand if condition to allow more build types other than QA

* [SG-747] removed execution condition

Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com>

* Apply suggestions from code review

Linter suggestions

Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com>
Co-authored-by: Micaiah Martin <mmartin@bitwarden.com>
Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com>
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com>

* Base bundle ID refactor and cleaned up TODOs

- Added base vars for the bundle IDs
- Removed a TODO and explained the remaining ones
- Commented a unused var, keeping it in the code as this might be useful later

Co-authored-by: Micaiah Martin <mmartin@bitwarden.com>
Co-authored-by: Federico Andrés Maccaroni <fedemkr@gmail.com>
Co-authored-by: Todd Martin <tmartin@bitwarden.com>
Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: mimartin12 <77340197+mimartin12@users.noreply.github.com>
Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com>
2022-11-01 22:14:49 +00:00
Carlos Gonçalves
5a4c81bd75 SG-769 - Ensured token for decoding is refreshed before use (#2150) 2022-10-31 15:46:10 -04:00
Carlos Gonçalves
ee09c0abda SG-786 - Fix 400 error code log outs without invalid_grant (#2156)
* SG-786 - Added validation to check if the 400 error is invalid grant

* SG 786 - Improved code quality
2022-10-31 13:40:26 -04:00
André Bispo
9baa79e10b [SG-773][SG-775] Duplicate passwordless login requests (#2160)
* [SG-773] Change method call to message send

* [SG-773] Introduce lock to avoid concurrent executions of login requests

* [SG-773][SG-775] add comment

* [SSG-773][SG-775] Refactor passwordlessLoginRequest string to constant
2022-10-31 15:57:09 +00:00
André Bispo
a8909a3ce6 [SG-765] Add cancel button to passwordless login modal on android (#2159) 2022-10-31 15:06:37 +00:00
André Bispo
b9b9c2e5ff [SG-166] Two Step Login - Feature Branch (#2157)
* [SG-166] Update fonts to have necessary icons

* [SG-166] Add new custom view to hold a button with a font icon and a label.

* [SG-166] Two Step login flow - Mobile (#2153)

* [SG-166] Add UI elements to Home and Login pages. Change VMs to function with new UI. Add new string resources.

* [SG-166] Pass email parameter from Home to Login page.

* [SG-166] Pass email to password hint page.

* [SG-166] Remove remembered email from account switching.

* [SG-166] Add GetKnownDevice endpoint to ApiService

* [SG-166] Fix GetKnownDevice string uri

* [SG-166] Add Renderer for IconLabel control. Add RemoveFontPadding bool property.

* [SG-166] include IconLabelRenderer in Android csproj file

* [SG-166] Add new control. Add styles for the control.

* [SG-166] Add verification to start login if email is remembered

* [SG-166] Pass default email to hint page

* [SG-166] Login with device button only shows if it is a known device.

* [SG-166] Change Remember Email to Remember me. Change Check to Switch control.

* [SG-166] Add command to button for SSO Login

* Revert "[SG-166] Update fonts to have necessary icons"

This reverts commit 472b541cef.

* [SG-166] Remove IconLabel Android renderer. Add RemoveFontPadding effect.

* [SG-166] Update font with new device and suitcase icon

* [SG-166] Fix RemoveFontPadding effect

* [SG-166] Remove unused property in IconLabel

* [SG-166] Fix formatting on IconLabelButton.xaml

* [SG-166] Update padding effect to IconLabel

* [SG-166] Add control variable to run code once on create

* [SG-166] Add email validation before continue

* [SG-166] Refactor icons

* [SG-166] Update iOS Extension font

* [SG-166] Remove HomePage login btn step

* [SG-166] Make clickable area smaller

* [SG-166] Fix hint filled by default

* [SG-166] Fix IconButton font issue

* [SG-166] Fix iOS extension

* [SG-166] Move style to Base instead of platforms

* [SG-166] Fix layout for IconLabelButton

* [SG-166] Switched EventHandler for Command

* [SG-166] Removed event handler

* [SG-166] Fix LoginPage layout options

* [SG-166] Fix extensions Login null email

* [SG-166] Move remembered email logic to viewmodel

* [SG-166] Protect method and show dialog in case of error

* [SG-166] Rename of GetKnownDevice api method

* [SG-166] rename text resource key name

* [SG-166] Add close button to iOS extension

* [SG-166] Switch event handlers for commands

* [SG-166] Change commands UI thread invocation.

* [SG-166] Remove Login with device button from the UI

* [SG-166] Fixed appOptions and close button on iOS Extensions
2022-10-28 23:10:41 +01:00
github-actions[bot]
89adab6784 Autosync the updated translations (#2155)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-10-28 02:40:47 +02:00
mp-bw
77d8afcfe6 added missing id check to obj comparison (#2154) 2022-10-27 12:09:33 -04:00
Michał Chęciński
4fcb063190 Move renovate.json to .github folder (#2152) 2022-10-27 15:49:29 +02:00
mp-bw
5deba15373 Updated avatar color selection logic (#2151)
* updated avatar color selection logic

* tweaks

* more tweaks

* formatting
2022-10-26 12:34:54 -04:00
Carlos Gonçalves
505426cd6a [SG 547] Mobile username generator iOS.Extension UI changes (#2140)
* [SG-547] - Added button to generate username when using iOS extension

* [SG-547] - Missing changes from last commit

* SG-547 - Added missing interface method

* SG-547 - Added token renovation for iOS.Extension flow

* SG-547 Replaced generate buttons for icons

* SG-547 Removed unnecessary validation

* SG-547 - Fixed PR comments

* SG 547 - Missing file from last commit

* SG-547 - Fixed PR comments

* SG-547 - Renamed method
2022-10-25 21:05:15 +01:00
github-actions[bot]
3cb9f37997 Autosync the updated translations (#2146)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2022-10-21 12:18:18 +02:00
aj-rosado
4c2f5c05e5 [PS-1188] Add new Solarized Dark Theme (#2141)
* PS-1188 Add new Solarized Dark Theme
2022-10-19 10:57:01 +01:00
André Bispo
eefc9bd239 [SG-705] Popup when a request for authentication comes in on a logged-in account that is not active (#2135)
* [SG-705] Added pop up to perform account switching if the user receives a login request from another account.

* [SG-705] missing resource designer

* [SG-705] Fixed wrong key for state service variable.

* [SG-705] Fix formatting of account switch alert.

* [SG-705] dotnet format run

* [SG-705] Removed async

* [SG-705] Refactor on App
2022-10-18 17:21:45 +01:00
sneakernuts
d18efdea73 updated version bump + crowdin-pull workflows (#2119)
* updated version bump + crowdin-pull workflows

* updated retrieve secrets step and changed gpg vars to reference kv secrets

* Removed duplicate step

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2022-10-17 15:19:06 +00:00
Michał Chęciński
a72a0ec0ce Fix autobump workflow (#2138) 2022-10-17 11:50:38 +02:00
André Bispo
c7e9f30a9a [SG-703] Login request is not removed after dismissing push notification (#2134)
* [SG-703] Added category to iOS notifications in order to be able to receive dismiss actions

* [SG-703] PR Fix
2022-10-14 17:35:17 +01:00
mp-bw
6c404c8229 Testing removal of Xamarin.Forms dependency from iOS.ShareExtension (#2137) 2022-10-14 15:53:18 +01:00
github-actions[bot]
e5de530c2c Autosync the updated translations (#2133)
Co-authored-by: github-actions <>
2022-10-14 14:00:38 +02:00
github-actions[bot]
3a87378847 Bumped version to 2022.10.1 (#2132)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-10-13 12:43:48 -07:00
Danielle Flinn
6048a10d6d [PS-1543]Sentence case mobile (#2095)
* transitioned to sentence case
* removed unnecessary punctuation
* other small content improvements
2022-10-13 12:31:03 -07:00
github-actions[bot]
a5ad43b134 Bumped version to 2022.10.0 (#2130)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-10-12 17:42:14 +01:00
André Bispo
539f8fe5b5 [SG-690] Timeout fix (#2129)
[SG-690] Updated server times to utc
2022-10-12 15:55:38 +01:00
André Bispo
569922805f [SG-703] Login request is not removed after dismissing push notification (#2125)
* [SG-703] Handle iOS dismiss notification action. Added core logic to remove passwordless notification from local storage.

* [SG-702] Added broadcast receiver to catch dismiss notfication events on android.

* [SG-703] PR fixes.

* [SG-703] Fix constants namespaces. Lazyloading services on broadcast receiver.

* [SG-703] Change services to use lazy loading

* [SG-703] Change lazy loading to be parameterless.
2022-10-12 15:55:01 +01:00
github-actions[bot]
3972e3de8a Autosync the updated translations (#2118)
Co-authored-by: github-actions <>
2022-10-12 15:14:24 +02:00
Federico Maccaroni
ba677a96aa [EC-519] Refactor Split DeviceActionService (#2081)
* EC-519 Refactored IDeviceActionService to be split into IFileService and IAutofillManager also some cleanups were made

* EC-519 Fix format

* EC-519 Fix merge to use the new AutofillHandler
2022-10-11 18:19:32 -03:00
Federico Maccaroni
d800e9a43e EC-602 Constants namespace fix (#2127) 2022-10-11 16:47:52 -04:00
Federico Maccaroni
2d35a00caa EC-602 added droid constants file with the package name constant (#2126) 2022-10-11 16:08:36 -04:00
Michał Chęciński
dc5698b353 [DEVOPS-1014] Fix version auto bump workflow (#2121)
* Fix autobump

* Fix regex

* Use tag name

* DEVOPS-1014 - Review (#2123)

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2022-10-11 16:27:07 +02:00
André Bispo
abada481b7 [SG-702] Tapping Push Notification does not open the account the request is for (#2112)
* [SG-702] Tap notification now switches accounts if it is a passwordless notification.

* [SG-702] Fix compilation errors

* [SG-702] Fixed iOS notification tap fix

* [SG-702] Notification data model

* [SG-702] Change method signature with object containing properties. PR fixes.
2022-10-07 12:06:57 +01:00
André Bispo
1e5eab0574 [SG-690] DateTime to Utc fix (#2115)
* [SG-690] DateTime to Utc fix

* [SG-690] Removed Utc from server side datetime.
2022-10-04 20:40:28 -04:00
André Bispo
c1101af582 [SG-687] added try catch to cancellation token disposal. (#2114) 2022-10-04 20:25:52 +01:00
André Bispo
1db4c4fc8b SG-687 Request time not updating (#2108)
* [SG-687] request updates text with timer task and expires after 15 mins.

* [SG-697] PR Fixes

* [SG-687] Ran code format

* [SG-687] PR Fixes

* [SG-687] missed constant replacement

* [SG-687] PR fixes
2022-10-03 17:42:33 +01:00
André Bispo
bc949fe87a [SG-691] Login request is not displayed after changing accounts (#2111)
* [SG-691] Added new message to be broadcasted when account is switched to trigger a check for login requests.

* [SG-691] PR fixes
2022-10-03 17:11:38 +01:00
Carlos Gonçalves
a890ee6612 [SG-666][SG-667] Email is not prefilled and username isn't generated automatically (#2109)
* SG-666 SG-667 - Email is now prefilled for plus addressed email username type
* Username is auto generated upon navigation

* SG-666 - Fixed PR comments
* Added missing property initialization
2022-10-03 16:51:22 +01:00
André Bispo
90e0b5dcf0 [SG-690] Login Request does not disappear after 15 minutes (#2106)
* [SG-690] Add timeout of 15 for android notifications. Add condition to not prompt login requests if 15mins have passed. Add constant for timeout time.

* [SG-690] Added dialog on click confirm/deny if the request is expired.

* [SG-690] PR fixes

* [SG-690] PR fixes
2022-09-30 20:44:56 +01:00
mp-bw
9631988fc2 remove duplicate/older community toolkit dependency (#2107) 2022-09-30 15:44:18 -03:00
André Bispo
b5f80da28d [SG-696] Android notification icon blank (#2105)
* [SG-696] Added new icon for android notifications. Changed notification intent flags to use helper.

* [SG-696] PR fix. Min version was already 21.
2022-09-30 14:47:03 +01:00
github-actions[bot]
7e0b943b70 Autosync the updated translations (#2104)
Co-authored-by: github-actions <>
2022-09-30 13:10:59 +02:00
mp-bw
425be32c15 added a11y disclosure prompt for Android (#2102) 2022-09-27 15:03:06 -03:00
André Bispo
f9a32e4abc Passwordless feature branch PR (#2100)
* [SG-471] Passwordless device login screen (#2017)

* [SSG-471] Added UI for the device login request response.

* [SG-471] Added text resources and arguments to Page.

* [SG-471] Added properties to speed up page bindings

* [SG-471] Added mock services. Added Accept/reject command binding, navigation and toast messages.

* [SG-471] fixed code styling with dotnet-format

* [SG-471] Fixed back button placement. PR fixes.

* [SG-471] Added new Origin parameter to the page.

* [SG-471] PR Fixes

* [SG-471] PR fixes

* [SG-471] PR Fix: added FireAndForget.

* [SG-471] Moved fire and forget to run on ui thread task.

* [SG-381] Passwordless - Add setting to Mobile (#2037)

* [SG-381] Added settings option to approve passwordless login request. If user has notifications disabled, prompt to go to settings and enable them.

* [SG-381] Update settings pop up texts.

* [SG-381] Added new method to get notifications state on device settings. Added userId to property saved on device to differentiate value between users.

* [SG-381] Added text for the popup on selection.

* [SG-381] PR Fixes

* [SG-408] Implement passwordless api methods (#2055)

* [SG-408] Update notification model.

* [SG-408] removed duplicated resource

* [SG-408] Added implementation to Api Service of new passwordless methods.

* removed qa endpoints

* [SG-408] Changed auth methods implementation, added method call to viewmodel.

* [SG-408] ran code format

* [SG-408] PR fixes

* [SG-472] Add configuration for new notification type (#2056)

* [SG-472] Added methods to present local notification to the user. Configured new notification type for passwordless logins

* [SG-472] Updated code to new api service changes.

* [SG-472] ran dotnet format

* [SG-472] PR Fixes.

* [SG-472] PR Fixes

* [SG-169] End-to-end testing refactor. (#2073)

* [SG-169] Passwordless demo change requests (#2079)

* [SG-169] End-to-end testing refactor.

* [SG-169] Fixed labels. Changed color of Fingerprint phrase. Waited for app to be in foreground to launch passwordless modal to fix Android issues.

* [SG-169] Anchored buttons to the bottom of the screen.

* [SG-169] Changed device type from enum to string.

* [SG-169] PR fixes

* [SG-169] PR fixes

* [SG-169] Added comment on static variable
2022-09-26 18:27:57 +01:00
André Bispo
2f4cd36595 [SG-671] OTP Menu Screen causes Crash on Android (#2097)
* [SG-671] removed unnecessary calc of otpauth period. protected cal of otpauth from crashing the app if url has a wrong format.

* [SG-671] changed logger

* [SG-671] Refactored GetQueryParams code to used HttpUtility.ParseQueryString.

* [SG-671] refactor and null protection.

* [SG-671] code format

* [SG-671] fixed bug where totp circle countdown was fixed to 30.

* [SG-167] added fallback for uri check. Changed all default totp timers to constant.

* [SG-671] missed unsaved file

* [SG-671] simplified code
2022-09-26 17:51:03 +01:00
github-actions[bot]
70ee24d82a Autosync the updated translations (#2099)
Co-authored-by: github-actions <>
2022-09-26 15:58:33 +02:00
github-actions[bot]
28576bbf49 Autosync the updated translations (#2096)
Co-authored-by: github-actions <>
2022-09-23 02:46:20 +02:00
mp-bw
7f9dfd3dae Updated libs to latest stable (#2092)
* updated libs to latest stable

* testing rollback of test dependencies

* testing xunit restore

* bump all test libs except xunit

* remove AndroidX.Migration package
2022-09-22 11:17:17 -04:00
github-actions[bot]
115aee2026 Bump version to 2022.9.2 (#2090)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-21 14:23:27 -06:00
aj-rosado
86fee6f04e [PS-1312] Update NFCAdapter mutability flag (#2088)
* PS-1312 - Changed NFC Adapter PendingIntent to mutable
2022-09-21 17:43:48 +01:00
mp-bw
2deab59b35 [Android] Added shadowless version of monochrome launcher icon (#2084)
* Added shadowless version of monochrome launcher icon

* added missing project ref
2022-09-20 10:25:12 -04:00
aj-rosado
b4737457a8 PS-1404 Improved exception handling when saving an attachment (#2078)
* PS-1404 Improved exception handling when saving an attachment

* PS-1404 Reverted unnecessary catch change

* PS-1404 Added missing whitespace

* PS-1404 Improved code formatting

* PS-1404 removed unnecessary whitespace

* PS-1404 Using SubmitAsyncCommand on xaml and removed the click method from the cs file
2022-09-16 18:28:20 +01:00
aj-rosado
305c770c58 [PS-1312] Migration to android12 and new splashscreen (#2063)
* [PS-1312] Updated Android Target and Framework to Android12 and updated new SplashScreen for Android12

* PS-1312 Changed PendingIntents mutability

* PS-1312 Removed unused imports

* PS-1312 Added method to helper to add mutability option according to Android version

* PS-1312 Renamed helper method AddPendingIntentMutability and fixed validation

* PS-1312 Improved PendingIntentMutability method from helper readability and naming
2022-09-16 16:44:15 +01:00
github-actions[bot]
afa9e23707 Autosync the updated translations (#2077)
Co-authored-by: github-actions <>
2022-09-16 03:26:44 +02:00
André Filipe da Silva Bispo
87fb5cf2ae [SG-659] Fixed TOTP not showing for free user with classic plans. (#2071)
* [SG-659] Fixed TOTP not showing for free user with classic plans.

* [SG-659] Ran code format
2022-09-14 14:45:39 +01:00
github-actions[bot]
3f8e00985c Bump version to 2022.9.1 (#2069)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-12 11:47:52 -06:00
github-actions[bot]
533928a4f1 Bump version to 2022.9.0 (#2068)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-12 10:58:58 -06:00
Federico Maccaroni
b7048de2a1 [EC-528] Refactor Custom Fields into separate components (#1662)
* Refactored CustomFields to stop using RepeaterView and use BindableLayout and divided the different types on different files and added a factory to create them

* Fix formatting
2022-09-09 15:58:11 -03:00
github-actions[bot]
2016eadb0d Autosync the updated translations (#2057)
Co-authored-by: github-actions <>
2022-09-09 20:25:28 +02:00
Federico Maccaroni
68b5bc0964 EC-540 fix AppResources encoding to be UTF-8 with BOM (#2065) 2022-09-09 14:41:16 -03:00
Michał Chęciński
119fc5812b Update deprecated Azure Key Vault in workflows (#2059) 2022-09-05 11:39:22 +02:00
aj-rosado
b628c1990e [PS-191] Improve support for larger fonts Android (#2053)
* PS-191 Removed android native font scaling

* PS-191 Added exception handling when disabling android font scale
2022-08-31 12:08:36 +01:00
Thomas Rittson
183bfa0ab2 Update PR template (#2051) 2022-08-30 10:36:59 +10:00
Carlos Gonçalves
b1fb867b6e [SG-223] Mobile username generator (#2033)
* SG-223 - Changed page title and password title

* SG-223 - Refactored generated field
* Changed position of generated field
* Replaced buttons generate and copy for icons

* SG-223 - Refactor type to passwordType

* SG-223 - Added password or username selector
* Added string for label type selection

* SG-223 - Added logic for different types of username
* Added strings of new types

* [SG-223] - Added UI components for different username types
* Added static strings for new labels
* Added viewmodel properties to support username generation and their respective options

* [SG-223] Added control over type picker visibility

* [SG-223] Refactored username entry on add edit page and added generate icon
* Added GenerateUsername command

* [SG-223] - Implemented service for username generation

* [SG-223] - Added support for username generation for item creation flow
* Implemented cache for username options
* Added exception handling for api calls

* [SG-223] - Remove unused code

* [SG-223] - Added a new display field for username generated and respective command
* Added description label for each type of username
* Changed defautl value of username from string.Empty to -

* [SG-223] - Removed some StackLayouts and refactored some controls

* [SG-223] - Refactored properties name

* [SG-223] - Added visibility toggle icon for api keys of forwarded email username types

* [SG-223] - Refactored nested StackLayouts into grids.

* [SG-223] - Refactor and pr fixing

* [SG-223] - Removed string keys from Resolve
- Added static string to resources

* [SG-223] - Refactored Copy_Clicked as AsyncCommand
- Improved exception handling
- Refactored TypeSelected as GeneratorTypeSelected

* [SG-223] - Renamed PasswordFormatter

* [SG-223] - Refactored VM properties to use UsernameGenerationOptions
* Removed LoadUsernameOptions

* [SG-223] - Refactored added pickers to use SelectedItem instead SelectedIndex
* Deleted PickerIndexToBoolConverter as it isn't needed anymore

* [SG-223] -  Refactored and simplified Grid row and column definitions

* [SG-223] - Refactored Command into async command
* Added exception handling and feedback to the user

* [SG-223] - Refactored GeneratorType picker to use Enum GeneratorType instead of string

* [SG-223] - Changed some resource keys

* [SG-223] - Refactor method name

* [SG-223] - Refactored code and added logs for switch default cases

* [SG-223] - Added flag to control visibility when in edit mode

* [SG-223] - Added suffix Parenthesis to keys to prevent future conflicts

* [SG-223] - Refactored multiple methods into one, GetUsernameFromAsync
* Removed unused Extensions from enums

* [SG-223] - Added exception message

* [SG-223] - Added localizable enum values through LocalizableEnumConverter

* [SG-223] - Fixed space between controls

* [SG-223] - Removed unused code and refactored some variables and methods names

* [SG-223] - Removed unused code and refactored constant name to be more elucidative

* [SG-223] - Removed unused variable
2022-08-26 19:32:02 +01:00
manofthepeace
673ba9f3cc Fix Content Type for file upload (#2031) 2022-08-26 14:58:54 +01:00
github-actions[bot]
cdd9a5ff4d Autosync the updated translations (#2050)
Co-authored-by: github-actions <>
2022-08-26 10:07:32 +02:00
Federico Maccaroni
d204e812e1 EC-487 Added helper to localize enum values and also a converter to use in xaml (#2048) 2022-08-23 12:34:29 -03:00
André Filipe da Silva Bispo
9163b9e4de [SG-599] Cannot read authenticator key if you don't include URI before TOTP Secret. (#2047)
Removed unnecessary code when adding a TOTP auth key secret manually
2022-08-23 15:48:45 +01:00
André Filipe da Silva Bispo
ecd4da08ee [SG-598] Removed space from copied totp code (#2046)
Removed space from copied totp code
2022-08-23 15:04:17 +01:00
github-actions[bot]
525288d804 Autosync the updated translations (#2042)
Co-authored-by: github-actions <>
2022-08-19 12:51:41 +02:00
André Filipe da Silva Bispo
e829279e29 [SG-416] Updates to Bitwarden Authenticator (Feature Branch) (#2041)
* Initial commit of new TOTP page

* Revert config files from previous commit

This reverts commit b02c58e362.

* clear extra code and fix build

* add tab page

* add authentication view cell

* add toolbar icons

* got the countdown working

* enable context loading and vm init

* PS-70 Added toggle to quickly filter TOTP cypher items and show their details, Added new text resource

* PS-70 removed old authentication tab

* removed unnecessary code on vm

* fixed formatting

* PS-70 Added circular progress to the OTP count down

* PS-70 Fixed grid cell width. Added red progress at 20 percent. Refactored circular progress view.

* PS-70 Added new props to custom control.

* PS-70 show toggle only if it's premium

* PS-70 removed unnecessary code

* PS-70 add copy to clipboard.

* PS-70 show upgrade to premium text on details to free user.

* PS-70 added text labels to resource files

* PS-70 Renamed TOTP to Totp to have consistency in naming. Removed a11y text of switch because android was overlapping text.

* PS-70 added new UI to enter code manually in the QR Code scanner screen. Changed existing labels on scanner screen.

* PS-70 Splited totp code to adjust spacing.

* PS-70 Added scanner square corner overlay. Added scanning animation. Added scan success animation.

* PS-70 let zxing scanner camera feed on until screen is closed.

* PS-70 fixed scanner animation for android devices

* PS-70 added vibrate permission to manifest. refactored scanpage code. added manual authentication key feature in scanner.

* PS-70 fixed totp cell title label font

* PS-70 added copy button to totp edit cipher. Added row button when totp is null.

* PS-70 changed labels on manual scanner screen

* PS-70  Added label on top of button to solve UI bug.

* PS-70 Fixed android button overlapping bug by  adding button styling to a Frame view and placing a label inside. Fixed Color on scanner page.

* PS-70 Added frame styling for iOS, since frame view has different base configuration for android and iOS.

* PS-70 fixed font clipping bug on iOS

* PS-70 removed shadow for newer versions of android

* PS-70 code format

* PS-70 removed update to premium uri launch

* PS-70 PR fix for AppResource vs code behind generation.

* PS-70 changed premium required label. fixed bug when to show premium required label.

* [SSG-416] Removed the dashes from free user and just left the Premium subscription required.

* [SSG-416] removed unnecessary changes to the TabsPage file

* [SSG-416] removed unnecessary using.

* [SSG-416] Updated ViewPageViewModel and code refactoring.

* [SG-416] Updated scanner mode toggle text color to be inline with figma designs

* [SSG-416] Mobile PR Fixes

* [SSG-416] Add to remove a11y text from totp toggle because on android it places an helper text next to the switch making it invisible. Also removed from the label because it already reads the text from the label

* [SSG-416] run dotnet tool run dotnet-format

* [SSG-416] PR fixes

* Revert "[SSG-416] PR fixes"

This reverts commit 2f2b90acee.

* [PS-416] Fixed a bug where the item details page was not updating after saving.

* [SG-416] Authenticator toggle remake  (#2027)

* [SG-416] Removed toggle to TOTP. Added on MainPage new entry to go to screen with TOTP codes. Added filter for TOTP codes to be used when searching.

* [SG-416] Removed unnecessary code. Added nav back if there is only 1 cipher with totp code and the user removes it.

* [SG-416] Run dotnet format tool

* [SG-416] PR fixes

* [SG-416] PR Fixes. Manifest formatting. Add try catch. Extracted method and added null protection.

* [SG-416] Make TOTP codes appear above favourites.

* [SG-416] PR fixes. Show error dialog.

Co-authored-by: Carlos J. Muentes <42616259+cmuentes@users.noreply.github.com>
Co-authored-by: Jacob Fink <jfink@bitwarden.com>
2022-08-17 22:10:16 +01:00
aj-rosado
3d9555d420 [PS-1009] Changed keyboard on Passphrase generator to not allow emojis (#2038)
* PS-1009 Added effect to Entry that doesn't allow keyboard with emojis on passphrase separator

* PS-1009 Removed unnecessary ImeOptions setting from NoEmojiKeyboardEffect
Improved code

* PS-1009 Removed unnecessary null validation on Android's NoEmojiKeyboardEffect

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-08-16 18:54:28 +01:00
Deividt Gemeli
5f7a1e769a Changed the right margin of the switch at Create Account(#2021) 2022-08-16 18:52:28 +01:00
Joseph Flinn
8b118408fa Add workaround for broken windows 2022 runner (#2040)
* Add workaround for broken windows 2022 runner

* Adding workaround to F-Droid job
2022-08-16 09:46:54 -07:00
github-actions[bot]
de41845e3e Bumped version to 2022.8.1 (#2039)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-16 08:01:17 -07:00
aj-rosado
61597585b5 [PS-1219] Crash when login with SSO (#2023)
* PS-1219 Added null checks and improved error handling on SSO Login

* PS-1219 Improved code

* PS-1219 Improved const naming

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-08-16 12:05:23 +01:00
Michał Chęciński
e04b250a73 Add renovate config (#2034) 2022-08-12 13:54:57 +02:00
github-actions[bot]
4fbe1b40e3 Autosync the updated translations (#2035)
Co-authored-by: github-actions <>
2022-08-12 11:32:58 +02:00
Federico Maccaroni
3ef5b576ac [EC-371] Fix iOS extensions login more menu (#2016)
* EC-371 fix iOS extensions login more menu and HintPage to display correctly on extensions

* EC-371 fix merge
2022-08-08 11:28:48 -03:00
github-actions[bot]
570b56364a Autosync the updated translations (#2030)
Co-authored-by: github-actions <>
2022-08-05 14:30:57 +02:00
Federico Maccaroni
ae4e8e2d8e [EC-341] Fix show alternative 2FA on iOS extensions (#2011)
* EC-341 Fix show alternative 2FA on iOS extensions

* EC-341 Fix iOS.Core.csproj reference
2022-08-04 16:42:41 -03:00
Todd Martin
2c8406d0ad [ENG-71] Add Github deployment to release pipeline. (#2022)
* Added Github deployment to release pipeline.

* Added explicit expression syntax

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* Added explicit expression syntax

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* Added initial-status

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* Removed in_progress update since it's set on initial status

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

Co-authored-by: Todd Martin <>
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2022-08-04 13:47:49 -04:00
Michał Chęciński
94bd5ceed3 Fix Android build (#2029)
Change worker to win-2022 for VS2022
2022-08-04 12:35:17 -04:00
github-actions[bot]
aa6be3d691 Bumped version to 2022.8.0 (#2028)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-04 17:28:54 +02:00
Vince Grassia
97fe65647a Update Crowdin action hash (#2025) 2022-08-03 16:51:41 -04:00
Michał Chęciński
ee8b8866e0 Auto bump mobile version after release (#2013)
* Add autobump version number workflow

* Comment pr since not tested

* Update .github/workflows/version-auto-bump.yml

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>

* Trigger version bump workflow

* Comment for testing

* add input for testing

* FIx

* Remove testing values

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-08-02 09:34:07 +02:00
Michał Chęciński
3128a4c5c8 Add autobump workflow stub (#2015) 2022-07-29 12:18:57 +02:00
aj-rosado
8ec6545bbc [PS-1116] Improved network error handling (#2007)
* PS-1116 Improved network error handling on ViewPageViewModel and AddEditPageViewModel

* PS-1116 Renamed ViewPage and AddEditPage pages to a more explicit name.
Refactored CheckPassword from the CipherPages to a single Base class.

* PS-1116 Updated variables relative to the AddEditPage and ViewPage refactor to CipherAddEditPage and CipherDetailPage

* Renamed CipherDetailPage to CipherDetailsPage

* Code improvement

* PS-1116 Improved code formatting

* PS-1116 Improved formatting

* PS-1116 Improved code formatting
2022-07-27 17:46:56 +01:00
Federico Maccaroni
90a6850d76 EC-366 Updated AndroidManifest format as what VS4M autoformats (#2014) 2022-07-26 13:08:05 -03:00
Andreas Coroiu
16f70dc0ce [EC-348] change blacklisted to blocked URIs (#2012)
* [EC-348] update blacklisted to blocked URIs

* [EC-348] update variable names
2022-07-26 09:55:16 +02:00
github-actions[bot]
f0ebc5e644 Bumped version to 2022.6.3 (#2005)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-07-22 09:51:35 -07:00
Andreas Coroiu
03c5dd78c1 [EC-353] sentence cases (#2004) 2022-07-22 10:16:50 +02:00
Andreas Coroiu
e2b6e99a0c feat: change to on and off (#2001) 2022-07-21 15:57:08 +02:00
aj-rosado
263aeef030 [PS-1080] Added text alternative to Boolean custom field icon (#2000)
* PS-1080 Added new accessibility text property to the custom field bool icon

* PS-1080 Added BoolValue property to the FieldView and added new AccessibilityProperty to bool icon
2022-07-21 09:53:39 +01:00
Carlos Gonçalves
f809170c51 [SG-467] Fix environment url validations (#1999)
* [SG-467] Fixed url validation so it works with or without http or https

* [SG-467] Validation has been refactored for simplicity.
2022-07-20 16:27:49 +01:00
Vince Grassia
c2fcc0ac52 Update 'Dry Run' path in Release workflow (#1997) 2022-07-19 15:01:03 -04:00
Federico Maccaroni
5e61fb0a14 EC-325 fix format (#1995) 2022-07-15 17:35:21 +01:00
Pedro da Rocha Pires
cf222bd0c3 [EC-325] Settings option to allow screen capture on Android (#1914)
* settings option to allow screen capture on Android

* Improved code on Screen Capture and added prompt to the user to allow screen capture

* EC-325 Removed async on OnCreate of MainActivity given that's not necessary anymore

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-07-15 16:13:10 +01:00
Micaiah Martin
cb0c52fb26 Add publish options to release workflow (#1994) 2022-07-15 07:30:14 -06:00
Michał Chęciński
c07c305384 Add version change check in the version bump workflow (#1992) 2022-07-15 11:08:38 +02:00
Federico Maccaroni
d2fbf5bdea EC-312 Fix crash on entering invalid credentials five times on Autofill (#1988) 2022-07-14 23:17:04 +01:00
Federico Maccaroni
2d2a883b96 EC-306 Fix crash happening on vietnamise when trying to go to Password Autofill on iOS given that the string was the same as Autofill Services and the comparison was misleading. Also refactored so that the action is on each item instead of having to compare to act (#1989) 2022-07-14 23:04:13 +01:00
Federico Maccaroni
1f2fb3f796 [EC-324] Added more logging for information on list crash (#1993)
* EC-324 Added more logging for trying to get more information on list out of range crash on AppCenter

* EC-324 Fix include on iOS.Core.csproj on iOS CollectionView files
2022-07-14 22:54:45 +01:00
Federico Maccaroni
8f3a4b98a5 EC-323 sanitize data on get first letters for avatar image creation (#1990) 2022-07-14 21:33:30 +01:00
Donkeykong307
70cf7431f7 Opera GX Autofill Support (#1855)
Added Opera GX Support for autofill
2022-07-14 21:27:53 +01:00
vincentvidal
f2ba86a62b Add support for iodé Browser (#1886) 2022-07-14 21:24:02 +01:00
Federico Maccaroni
292908f53f [EC-259] Added Account Switching to Share extension on iOS (#1971)
* EC-259 Added Account switching on share extension on iOS, also improved performance for this and exception handling

* EC-259 code formatting

* EC-259 Added account switching to Share extension Send view

* EC-259 Fixed navigation on share extension when a forms page is already presented

* EC-259 Fix send text UI update when going from the iOS extension

* EC-259 Improved DateTimeViewModel with helper property to easily setup date and time at the same time and applied on usage
2022-07-12 14:12:23 -03:00
Carlos Gonçalves
d621a5d2f3 [PS 920] Fix selfhosted url validations (#1967)
* PS-920 - Added feedback to user when saving bad formed URLs
* Added feedback to user when trying to perform login with bad formed URL

* PS-920 - Refactor to use AsyncCommand
*(missing file from previous commit)

* PS-920 - Fixed whitespace formatting

* PS-920 - Removed unused method

* PS-920 - Fixed validation
* Added comment for hard coded string

* PS-920 - Removed unused properties
* Fixed url validations
* Refactored method to local function

* PS-920 - Added exception handling and logging
* Added generic error message string to AppResources
2022-07-11 18:02:11 +01:00
Patrick H. Lauke
75e8276784 Use correct icon for checked/unchecked boolean (#1986)
Closes https://github.com/bitwarden/mobile/issues/1985
2022-07-11 10:48:19 -03:00
Andreas Coroiu
67f49a0591 [PS-686] Mobile update negative copy in settings (#1961)
* feat: update auto totp copy setting

* feat: update show icons settings

* feat: update auto add settings

* feat: update settings and options to sentence case

* feat: update translation keys

With the latest changes the translation keys had diverged from their contents.
This commit fixes that.

* fix: revert AndroidManifest changes

* chore: add todo comments to fix negative functions
2022-07-11 08:45:42 +02:00
Rui Tomé
cceded2a0f Updated the wording on the modal warning when deleting the account (#1982) 2022-07-08 09:28:23 +01:00
Federico Maccaroni
846d3a85a2 EC-308 Fix crash produced by creating avatar image on AccountSwitchingOverlayHelper and also added more logging to see when it happens. (#1983) 2022-07-07 20:24:29 +01:00
github-actions[bot]
7802da2b9c Bumped version to 2022.6.2 (#1981)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-07-07 09:07:08 -07:00
Rui Tomé
cd56a124d5 [EC-303] Add warning when vault timeout is set to "Never" (#1976)
* Added to AppResources.resx the message warning the user about setting the lockout option to "Never"

* Added the condition to check the newly selected option on the vault timeout settings

* Changed the wording on the warning as to reflect the mobile version

* Changed the vault timeout modal to have the ability to cancel

* Simplified the reversion of value if the user cancels the change
2022-07-07 16:35:58 +01:00
Matt Gibson
58a3662d0f Add user verification to reset password request (#1980)
We only need master password hash because this is currently
only used for sso password setting after auto-provisioning. Key
Connector is not involved in these accounts
2022-07-06 17:23:20 -05:00
Ben Pearo
6c7413e38c replace link to mobile section of contributing documentation with working link (#1978) 2022-07-06 11:43:07 -03:00
Federico Maccaroni
547e61a66b Fix formatting (#1975) 2022-07-05 17:44:45 -04:00
Federico Maccaroni
d246d1dece EC-297 Fix possible crash when copying password on cipher item view. Also improved a bit the code of copying commands (#1974) 2022-07-05 18:14:46 -03:00
Federico Maccaroni
e2502e2e0c Improved the ServiceContainer to be easier to use and not to have the service name hardcoded to register/resolve a service (#1865) 2022-07-05 18:14:31 -03:00
Federico Maccaroni
448cce38e1 Improved BroadcastService and added try...catch on async void callbacks (#1917) 2022-07-05 18:14:10 -03:00
Federico Maccaroni
dbc1e5ea3e Fix crash when trying to Focus an Entry from a background thread and improved the code so there are fewer direct access from the VM to the View (#1879) 2022-07-05 16:37:06 -04:00
github-actions[bot]
a6ddc2496f Bumped version to 2022.6.1 (#1969)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-29 13:25:08 -07:00
github-actions[bot]
d9a818279f Bumped version to 2022.6.0 (#1968)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-29 09:11:34 -07:00
Matt Gibson
6e2e613fee Add ssoToken to limit lifetime of SSO redirect (#1965) 2022-06-27 14:53:15 -05:00
mp-bw
109aeb49e4 [BEEEP] [PS-940] Support for dark theme selection while using Default (System) theme (#1959)
* support for dark theme selection while using Default (System) theme

* refinements
2022-06-21 14:59:30 -04:00
Joseph Flinn
c892e9fa57 Pin NuGet version (#1957)
* Pinning the version of NuGet to 5.x

* pinning NuGet verison to 5.9.x

* pinning NuGet to 5.9.0.7134

* pinning NuGet to 5.9.0
2022-06-16 14:48:09 -07:00
Federico Maccaroni
b2500557e7 SG-396 Fix tappable area after hiding account switching (#1956) 2022-06-16 17:09:50 -04:00
mp-bw
7c311fbb55 fix for missing personal items added prior to joining org with personal ownership policy (#1955) 2022-06-16 09:55:09 -04:00
mp-bw
f24388c1b5 separate init and showVaultFilter property set (#1954) 2022-06-15 16:18:30 -03:00
Federico Maccaroni
3aef86bd34 [SG-386] iOS Update user state when coming from background (#1952)
* SG-386 Updated active user when coming from background to the iOS app and the extension had switched users

* Added iOSExtensionActiveUserIdKey to preference keys

* Reorder iOS preference keys
2022-06-15 13:44:25 -03:00
mp-bw
c53a85cd50 [SG-390] Fix for missing org items with single org & personal ownership enabled (#1953)
* fix for missing org items with single org & personal ownership enabled

* fix for ui issue with vault filter state change on pull to refresh
2022-06-15 11:43:54 -03:00
mp-bw
448758a697 Additional logic around filter display (#1951) 2022-06-14 14:02:03 -03:00
Thomas Rittson
e51233bf9b Update README and CONTRIBUTING to use contributing.bitwarden.com (#1932) 2022-06-14 09:55:15 +02:00
Thomas Rittson
f9cbe43627 [PIQ-3] Add Github Actions to help manage issues and PRs (#1948)
* Add automatic responses and stale Github Actions
2022-06-14 10:24:44 +10:00
Joseph Flinn
5579817f9f Updating the release version check to use the new action (#1934)
* Updating the release version check to use the new action

* Update .github/workflows/release.yml

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2022-06-13 12:58:19 -07:00
mp-bw
51a5f58258 Fixed vault filter display issue with personal ownership policy (#1950) 2022-06-13 10:30:32 -03:00
mp-bw
388ad4e840 lib updates (#1949) 2022-06-10 12:24:02 -04:00
mp-bw
48a8d9ae35 Clipboard handling adjustments for Android 13 (#1947)
* Android 13 clipboard tweaks

* adjustments

* adjustments round 2
2022-06-10 12:02:17 -04:00
mp-bw
dd6003bd4f alphabetize org list in vault filter (#1945) 2022-06-08 17:20:23 -04:00
Federico Maccaroni
fba407f3b6 SG-210 Account Switching in Autofill (iOS) (#1909)
* SG-210 Set up account switching on Autofill iOS

* SG-210 Fix refresh after sync on autofill ciphers, also added account switching on lock view on autofill. Also fix possible crash when scrolling when no items were displayed and also fixed navigation when login in on an automatically logged out account.

* SG-210 Added reference on iOS.Core project

* Fix formatting on AccountManager

* SG-210 Fix background color for dark theme
2022-06-08 14:24:01 -03:00
mp-bw
88b406544b [SG-79] Add filter to search and preselect org in new cipher (#1944)
* Add filter to search and preselect org in new cipher

* formatting

* fixes
2022-06-08 09:39:53 -04:00
dwbit
3438ed94ce Changing Report crash logs to Submit crash logs (#1937)
* Changing Report crash logs to Submit crash logs

* Updated resources keys to match the new value

Co-authored-by: Federico Andrés Maccaroni <fedemkr@gmail.com>
2022-06-07 21:47:44 -03:00
Federico Maccaroni
ec71b21264 PS-785 Added logs for exceptions on UpdateTemplatedCell and the extra data on each ExtendedCollectionView usage (#1943) 2022-06-07 15:57:53 -03:00
Federico Maccaroni
b223f5f16e EC-255 fix crash when scanning TOTP; BeginInvokeOnMainThread doesn't bubble up the exception, just crashes because it throws the exception to the current main thread context; so it was changed to InvokeOnMainThreadAsync which does bubble up the exception. (#1942) 2022-06-07 10:46:48 -04:00
Carlos Gonçalves
0a64e4c918 PS-587 - Fix searchbar search textfield and icon colors for dark themes (#1941)
* PS-587 Fix searchbar search textfield and icon colors for dark themes

* PS-587 - PR corrections
2022-06-07 15:43:25 +01:00
mp-bw
9b41db962e Added monochrome element to adaptive icon for theme support in Android 13 (#1940) 2022-06-06 14:50:52 -03:00
Federico Maccaroni
43d3c7b5d7 Update PULL_REQUEST_TEMPLATE.md (#1938) 2022-06-06 10:30:59 -03:00
CarleyDiaz-Bitwarden-zz
8168089591 Updating icons for Linked and Boolean fields (#1935)
Update to icons used for Boolean and Linked to use real icons

Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com>
2022-06-02 16:43:14 -04:00
github-actions[bot]
6b55fc3032 Bumped version to 2022.05.1 (#1933)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-02 09:30:39 -04:00
github-actions[bot]
87ab42b155 Bumped version to 2022.05.0 (#1931)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-01 16:18:06 -04:00
André Filipe da Silva Bispo
98130e89de PS-689 Android: Accessibility - back buttons in search and vault > bin lack appropriate accessible name (#1929)
* PS-689 Added back buttons accessibility text

* PS-689 Changed resource key from "GoBack" to "TapToGoBack"

* PS-689: class rename
2022-06-01 20:50:19 +01:00
André Filipe da Silva Bispo
121f0e3628 PS-675 Added accessibility text to password show/hide toggles (#1926)
* PS-675 Added accessibility text to password show hide toggles

* PS-675 refactor string resource key name
2022-06-01 16:02:28 +01:00
mp-bw
8a3d88b3ce [SG-79] Mobile Vault Filter (#1928)
* [SG-79] Vault Filter

* Update vault button text after sync

* formatting

* cleanup

* cleanup
2022-05-31 13:34:54 -04:00
Federico Maccaroni
b8b41fe847 [PS-536] Fix vault blank after unlocking and back navigation (#1930)
* PS-536 Fix by hack vault blank after unlocking and back navigate when previous page has value on iOS

* PS-536 Added platform check to the hack so it doesn't affect Android performance given that's an issue particular for iOS
2022-05-27 17:17:08 -04:00
André Filipe da Silva Bispo
5bbef3ee16 [PS-676] Accessibility - "Options" expand/collapse control does not announce state (#1925)
* PS-676: Accessibility - "Options" expand/collapse control does not announce state
- Moved to click event to the stacklayout
- Added accessibility text to stacklayout
- Removed accessibility on views inside stacklayout

* PS-676 Changed event to command

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-27 14:52:19 +01:00
Carlos Gonçalves
9a2b6c8ec9 PS-593 - View model properties are now updated on main thread (#1927) 2022-05-27 14:16:45 +01:00
André Filipe da Silva Bispo
5272c99643 PS-674: Accessibility password generator toggles (#1924)
- Additional information for VoiceOver and TalkBack
- Added new labels

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-25 17:25:21 +01:00
André Filipe da Silva Bispo
43e9515a03 [PS-672] Accessibility - File/Text controls unintuitive, "Text" accessible name incorrect (#1923)
* PS-672: Accessibility - File/Text controls unintuitive, "Text" accessible name incorrect
- Added new text to help identify File / Text segmented button
- Added missing text for Text button accessibility

* PS-672: refactor code with pr suggestions

* PS-672: removed unnecessary code

* PS-672: change text from "click" to "tap"

* PS-672: removed comma

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-25 17:20:51 +01:00
André Filipe da Silva Bispo
7e9b7398c8 PS-90: App keeps asking for 2FA even though I've checked the "remember me" checkbox (#1921)
- Logout was removing the 2FA token, portion of code deleted.
- Clear saved token when 2FA error is returned by the server.

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-24 15:09:24 +01:00
Jake Fink
58d7b001a5 add master password reprompt to share sheet extension (#1922) 2022-05-23 16:01:04 -04:00
André Filipe da Silva Bispo
a259560d29 PS-592 Mobile - Name field is not prioritised in search results (#1907)
- Search method now gives priority to matches in the Name property

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-23 10:20:48 +01:00
André Filipe da Silva Bispo
22c746543a PS-518 - Add setting to block AppCenter / Analytics - Mobile (#1905)
* PS-518 - Add setting to block AppCenter / Analytics - Mobile
- Added another entry into Settings page under the Others section
- Added prompt to ask user to enable / disable Crash Reports
- Added compilation tags to remove if the build is FDroid 

* PS-518 Add setting to block AppCenter / Analytics - Mobile
- Reduced FDroid compilation tags throughout the code
- Added Init, Enable and State methods to Logger
- Simplified SettingsPageViewModel Enable/Disable code

* PS-518 Add setting to block AppCenter / Analytics - Mobile
- Appcenter references were removed from App project, 
- Removed FDroid build.yml code that was deleting Appcenter packages from App.csproj

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-18 17:59:19 +01:00
André Filipe da Silva Bispo
bcbc2738ca PS-591 Fix avoid ambiguous characters #1664 (#1906)
* PS-591 - iOS - Avoid ambiguous characters is activated inside the main client, but is deactivated when creating a vault item from the autofill prompt. #1664
- Refactor the name of the property Ambiguous to AvoidAmbiguous, this naming was misleading.
- Fixed bug where the boolean value for the AvoidAmbiguous property was being stored inverted.

* PS-591 - iOS - Avoid ambiguous characters is activated inside the main client, but is deactivated when creating a vault item from the autofill prompt. #1664
- Changed AvoidAmbiguous to AllowAmbiguous
- Added wrapper Prop to bind UI Toggle to VM

Co-authored-by: André Bispo <abispo@bitwarden.com>
2022-05-18 14:58:49 +01:00
Thomas Rittson
604e3b6892 Remove testing requirements from pr template (#1901) 2022-05-10 17:02:40 -04:00
mp-bw
b081a8c634 fix a11y issue with hCaptcha on iOS (#1896) 2022-04-28 15:30:33 -04:00
Federico Maccaroni
c251b950d1 PS-77 Updated two-factor email request to include the device identifier to check whether to send the 2fa email because of a new device (#1895) 2022-04-28 10:51:13 -03:00
github-actions[bot]
1bbe8d8b98 Bumped version to 2.18.1 (#1894)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-04-27 09:04:29 -07:00
github-actions[bot]
cdc41d3bef Bumped version to 2.18.0 (#1893)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-04-27 08:24:29 -07:00
Oscar Hinton
769851adfe Update .gitattributes to fix build issue (#1892) 2022-04-26 16:51:58 -04:00
Oscar Hinton
c988175e50 [TI-8] Add .git-blame-ignore-revs (#1891) 2022-04-26 11:27:13 -04:00
Oscar Hinton
04539af2a6 Run dotnet format (#1738) 2022-04-26 17:21:17 +02:00
Oscar Hinton
e0efcfbe45 Add dotnet-format tool (#1737) 2022-04-26 17:21:07 +02:00
Federico Maccaroni
57213607a7 PS-291 Fix password history to update the collection on the main thread to load correctly (#1890) 2022-04-25 17:43:55 -04:00
Federico Maccaroni
2cab62fda5 TDL-13 Removed workaround of null reference on LabelRenderer given that Xamarin forms has been updated with the fix (#1889) 2022-04-25 16:09:47 -03:00
sneakernuts
99828c7ead Switched org (#1887) 2022-04-22 13:35:19 +00:00
mp-bw
ab6dde4a11 update XF and revert old workarounds (#1885) 2022-04-21 21:29:47 -04:00
dwbit
80bd8ba9d1 Change source string 'copy notes' to 'copy note' (#1881)
Change the value for 'copy notes' to 'copy note' since it is applying a single action on 1 item. This source string is already 'copy note' in the browser extension.
2022-04-21 08:03:21 -04:00
dwbit
35853a3815 Contribution Documentation edits (#1880)
* Update crowdin manager and forum category

Updating Crowdin contact from Kyle to dwbit. Update 'User-to-User Support' forum category to 'Ask the Bitwarden Community'

* Text corrections

Correcting title of forum category

* Add 'category' to text change

Update the 'Ask the Bitwarden Community' text change.
2022-04-20 23:25:37 +01:00
mp-bw
cfbbea59e9 account switching in autofill UI (Android) (#1882) 2022-04-19 20:38:17 -04:00
Jake Fink
14d4b2ee29 [BEEEP] add context to search titles (#1878)
* add more descriptive titles to search pages

* add App Resources
2022-04-10 13:00:52 -04:00
github-actions[bot]
b6ad3527db Autosync the updated translations (#1877)
Co-authored-by: github-actions <>
2022-04-08 11:52:21 +02:00
mp-bw
88f6b60b97 Crash fixes (#1869)
* Crash fixes

* added HasAutofillService to DeviceActionService
2022-04-01 12:07:14 -04:00
github-actions[bot]
1f58b0cabe Autosync the updated translations (#1868)
Co-authored-by: github-actions <>
2022-04-01 12:12:08 +02:00
github-actions[bot]
284d728023 Autosync the updated translations (#1863)
Co-authored-by: github-actions <>
2022-03-25 01:21:43 +01:00
mp-bw
0796bf17ce Fix for missing token when checking for key connector migration (#1861) 2022-03-24 15:53:12 -04:00
github-actions[bot]
4bd06d2393 Bump version to 2.17.1 (#1859)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-22 12:41:49 -06:00
github-actions[bot]
a3a508eb83 Bump version to 2.17.0 (#1858)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-22 10:49:58 -06:00
Matt Portune
f10307c72d Remove verbose state & value storage debug logging (#1857) 2022-03-21 16:44:54 -04:00
Federico Maccaroni
840925c479 Added null checks for iOS crash OnActivated on KeyWindow (#1856) 2022-03-21 11:34:22 -04:00
github-actions[bot]
fdcb2d76c9 Bumped version to 2.16.5 (#1854)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-18 13:21:46 -07:00
jnolan912
4734fe4e43 Update ios Autofill page to match setting's name (#1354)
The option in the ios menu to get to autofill settings is called "Passwords" instead of "Passwords & Accounts"
2022-03-18 20:00:37 +00:00
Federico Maccaroni
383eee6ec7 Fixed flickering on iOS while loading collections for the collection crash hotfix (#1852) 2022-03-18 15:41:15 -03:00
github-actions[bot]
1d9671bc5c Autosync the updated translations (#1851)
Co-authored-by: github-actions <>
2022-03-18 01:18:33 +01:00
Federico Maccaroni
22b00bcb33 Fix iOS 15.4 crash from empty list to adding an item by awaiting after every header add; also added that on Settings just in case there is another crash scenario. (#1850) 2022-03-17 17:33:22 -03:00
Matt Portune
c1748acf39 Misc fixes for account switching (#1849)
* Misc fixes for account switching

* use unique bio integrity key in ShareExtension
2022-03-17 14:27:01 -04:00
Micaiah Martin
507c3faea1 Updated actions (#1848) 2022-03-17 09:24:42 -06:00
Chad Scharf
020a5c072d Update SECURITY.md (#1847)
* Update SECURITY.md

Add link to our HackerOne program for submitting potential security issues.

* Revise language on SECURITY.md
2022-03-15 15:54:45 -04:00
github-actions[bot]
c47aad0412 Bumped version to 2.16.4 (#1846)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-03-15 09:27:50 -07:00
Federico Maccaroni
7c83d7b37b Removed grouping from Settings to fix a crash on iOS 15.4 (#1845) 2022-03-15 10:19:34 -04:00
Federico Maccaroni
4d4e246a47 Fix #1745 crash on scroll of grouped collection view on iOS 15.4 beta (#1842) 2022-03-14 12:49:57 -04:00
github-actions[bot]
612e458071 Autosync the updated translations (#1839)
Co-authored-by: github-actions <>
2022-03-11 02:28:34 +01:00
Matt Portune
a2ec263116 fixes for font cutoff on samsung devices (#1838) 2022-03-10 13:55:48 -05:00
Micaiah Martin
5ade10d1fe Update input name to be consistent with other workflows (#1837) 2022-03-10 11:31:49 -07:00
Matt Portune
5008e1daa8 fixed issues with logging out inactive accounts (#1836) 2022-03-10 09:02:01 -05:00
Federico Maccaroni
ad7c656868 Prevent touches when root view is obscured by another screen on Android (#1835) 2022-03-10 09:55:58 -03:00
Joseph Flinn
bdd0ea007b Update hotfix release branch name to hotfix-rc (#1834) 2022-03-09 12:46:24 -08:00
Matt Portune
bf33f23c12 Fix for short profile Name value crashing app (#1833) 2022-03-09 09:00:04 -05:00
Matt Portune
c043528a16 fix for lock & logout message parsing issue (#1832) 2022-03-08 14:26:35 -05:00
Federico Maccaroni
fd74164f82 Remove Microsoft.AppCenter.Crashes from Core.csproj on FDroid on the build.yml (#1831) 2022-03-08 14:45:55 -03:00
Matt Portune
17cdc96352 fix for logging out active account from switcher and cleanup (#1830) 2022-03-07 15:15:21 -05:00
Vincent Salucci
fcc94d85af [Captcha] Implement for 2fa (#1827) 2022-03-07 12:39:38 -06:00
Matt Portune
79a76c4638 Support for lock/logout/remove accounts from account list (#1826)
* Support for lock/logout/remove accounts via long-press

* establish and set listview height before showing

* undo modification
2022-03-07 12:28:06 -05:00
stevenlele
efd83d07dd [SupportedBrowsers] add Alook (#1814) 2022-03-07 09:16:58 -05:00
github-actions[bot]
0f14aa242c Autosync the updated translations (#1825)
Co-authored-by: github-actions <>
2022-03-04 01:26:35 +01:00
Micaiah Martin
a33232dec0 Renewed certificates and profiles (#1823) 2022-03-03 11:20:34 -07:00
Matt Gibson
084072e485 Add url encoding to data parameter (#1822) 2022-03-02 11:32:43 -06:00
Federico Maccaroni
db7ca3b93e BEEEP: Abstract and Centralize Logging (#1663)
* Abstracted App Center Logging into its own component, so that we can have it centralized in one place and we avoid checking for FDroid on all the places we want to use it

* Implemented the new logger where Crashes.TrackError was being used except on some specific cases

* Improved logging, added a debug logger and removed AppCenter to be used on DEBUG
2022-03-02 14:15:16 -03:00
Matt Portune
34d0ecf64b support for per-user biometric state tracking (#1820) 2022-03-01 14:04:17 -05:00
Daniel James Smith
2076c11cbd Bump target framework to netcoreapp3.1 (#1817)
Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-02-28 12:04:09 -07:00
Micaiah Martin
4a508ea7a2 Added manual trigger for builds (#1819) 2022-02-28 11:30:27 -07:00
Matt Portune
9384b3e538 fixed migration and account removal issues (#1818) 2022-02-28 13:02:33 -05:00
Daniel James Smith
317e7dad9a BEEEP: Colorize hidden custom field when value visible (#1813) 2022-02-25 21:47:21 +01:00
github-actions[bot]
fac295c97b Autosync the updated translations (#1812)
Co-authored-by: github-actions <>
2022-02-25 12:29:47 +01:00
Matt Portune
f94812719d Apply Disable Favicon setting globally to match desktop (#1811)
* Apply Disable Favicon setting globally to match desktop

* streamline the approach to applying global settings
2022-02-24 17:13:00 -05:00
Matt Portune
be993bcd02 Fix for missing bio unlock on app restart (#1810) 2022-02-24 15:33:55 -05:00
Federico Maccaroni
c74ed668b5 Changed link on Settings "Change Master Password" and "Two Step Login" to go to the web vault settings. Also refactored a bit to reuse the urls (#1809) 2022-02-24 10:27:08 -03:00
Matt Portune
9201da8515 take environment into account when checking for existing account (#1808) 2022-02-23 15:30:49 -05:00
Matt Portune
2e8824ce05 Account Switching (#1807)
* Account Switching (#1720)

* Account switching

* WIP

* wip

* wip

* updates to send test logic

* fixed Send tests

* fixes for theme handling on account switching and re-adding existing account

* switch fixes

* fixes

* fixes

* cleanup

* vault timeout fixes

* account list status enhancements

* logout fixes and token handling improvements

* merge latest (#1727)

* remove duplicate dependency

* fix for initial login token storage paradox (#1730)

* Fix avatar color update toolbar item issue on iOS for account switching (#1735)

* Updated account switching menu UI (#1733)

* updated account switching menu UI

* additional changes

* add key suffix to constant

* GetFirstLetters method tweaks

* Fix crash on account switching when logging out when having more than user at a time (#1740)

* single account migration to multi-account on app update (#1741)

* Account Switching Tap to dismiss (#1743)

* Added tap to dismiss on the Account switching overlay and improved a bit the code

* Fix account switching overlay background transparent on the proper place

* Fixed transparent background and the shadow on the account switching overlay

* Fix iOS top space on Account switching list overlay after modal (#1746)

* Fix top space added to Account switching list overlay after closing modal

* Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well

* Usability: dismiss account list on certain events (#1748)

* dismiss account list on certain events

* use new FireAndForget method for back button logic

* Create and use Account Switching overlay control (#1753)

* Added Account switching overlay control and its own ViewModel and refactored accordingly

* Fix account switching Accounts list binding update

* Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755)

* Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756)

* Usability improvements for logout on vault timeout (#1781)

* accountswitching fixes (#1784)

* Fix for invalid PIN lock state when switching accounts (#1792)

* fix for pin lock flow

* named tuple values and updated async

* clear send service cache on account switch (#1796)

* Global theme and account removal (#1793)

* Global theme and account removal

* remove redundant call to hide account list overlay

* cleanup and additional tweaks

* add try/catch to remove account dialog flow

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-02-23 12:40:17 -05:00
Micaiah Martin
ded3f07fa6 Fixes incorrect path in workflow (#1806) 2022-02-23 10:29:40 -06:00
Micaiah Martin
31a3ec963b [BEEEP] - Added workflows to ignored paths (#1802)
Makes sure that edits to workflow files don't trigger a build.
2022-02-23 09:34:26 -06:00
Micaiah Martin
4722d2f632 Add dry run option to release workflow (#1801)
* Add dry-run to release workflow.
2022-02-23 08:48:07 -06:00
Federico Maccaroni
fa1bc3fa14 Changed Input keyboard on phone to be the telephone keyboard and also capitalized the keyboard on some fields of add/edit identity (#1800) 2022-02-23 11:07:54 -03:00
Federico Maccaroni
fa8d59075b Fix Options being seen in two lines on Add/edit Send (#1798) 2022-02-22 12:28:43 -05:00
Federico Maccaroni
23ca0f4b93 Fix icon image size to be adaptive on Large Font Size Accessibility which fixes row height on large vault (#1795) 2022-02-22 10:33:38 -05:00
Chad Scharf
04f4ad48f0 We're Hiring (#1797)
Added link to README.md for Bitwarden Careers page.
2022-02-22 10:28:46 -03:00
Micaiah Martin
a9be659e27 Moved to new Google Service Account (#1789) 2022-02-18 15:05:13 -07:00
Micaiah Martin
39596d7533 Moved to new Google Service Account (#1788) 2022-02-18 15:21:59 -06:00
Micaiah Martin
dd2c24dcc7 Move to using shared workflow (#1787) 2022-02-18 13:29:14 -06:00
github-actions[bot]
ad6cf9318b Autosync the updated translations (#1786)
Co-authored-by: github-actions <>
2022-02-18 10:20:24 +01:00
Federico Maccaroni
ea471b0749 Fixed some Large Font Accessibility issues on Vault and Send screens for Icons Display #1774 (#1785) 2022-02-17 19:34:22 -03:00
Micaiah Martin
dbaa32b02c Created initial workflow for workflow linting (#1783) 2022-02-16 15:26:11 -06:00
Matt Gibson
46128bcfe6 Enforce Hold label (#1779)
* Enforce Hold label

* Linting

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-02-16 08:43:46 -06:00
Federico Maccaroni
02562be8c7 Fix truncated bottom on Password generator when large font size is set on Android (#1782) 2022-02-15 19:10:43 -03:00
Joseph Flinn
95581bd4d9 Patch/release new build artifact name (#1778)
* Switching the iOS build artifact and release asset names

* disabling jobs/steps to test the new release asset name

* switching to download artifacts from rc

* testing the upload of the 'Bitwarden iOS' directory

* Build zip asset of the Bitwarden iOS asset

* trying a couple of different zip paths

* Final package test

* Re-enabling all of the jobs after testing
2022-02-15 07:57:21 -08:00
Jake Fink
aba34c38e9 remove erroneous autofill description (#1780) 2022-02-15 10:46:33 -05:00
github-actions[bot]
1af447c47f Bumped version to 2.16.3 (#1777)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-14 12:37:14 -08:00
Federico Maccaroni
4fb811ae87 Build: Upload dSYMs to AppCenter (#1776)
* Added dsym artifact to be uploaded alongside with the ipa o the build.yml

* Added dsym artifact to be uploaded alongside with the ipa o the build.yml

* Fixed build.yml dsym artifact

* Fix upload dsym build.yml

* Fix build.yml to check what gets exported and after this will become the adjustment for the dsym (disabling Android build for this test)

* Fix build.yml to copy all dsyms and artifact them (disabling Android build for this test)

* Fix build.yml to only copy all dsyms and ipa and artifact them (disabling Android build for this test)

* Added Appcenterr CLI and upload missing symbols for dSYM to the build

* Add secret to build workflow (#1771)

* Changed build.yml upload dsym command from upload-missing-symbols to upload-symbols

* Added restrictions for uploading iOS symbols to AppCenter on build.yml

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2022-02-14 11:06:35 -08:00
Matt Portune
3127295444 Fix for vault timeout not firing on resume (#1775)
* fix for vault timeout not firing on resume

* call ResumedAsync with FireAndForget
2022-02-14 11:29:04 -05:00
Vincent Salucci
615136be96 [Icons] - BUG - Update groupings icon for collections (#1773) 2022-02-12 12:27:37 +00:00
github-actions[bot]
e4230ac4f6 Autosync the updated translations (#1766)
Co-authored-by: github-actions <>
2022-02-11 22:47:48 +01:00
Jake Fink
15e9915da6 check email for null before authenticating (#1769)
* check email for null before authenticating

* add return after logging out and track error if email not found
2022-02-11 13:46:17 -05:00
Jake Fink
59ed76d956 remove datepicker style workaround (#1768) 2022-02-11 12:23:51 -05:00
github-actions[bot]
972755c725 Bump version to 2.16.2 (#1765)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-10 13:47:03 -07:00
Federico Maccaroni
92c40e2984 Improved code for periodic Autofocus on scan for better cancellation and task handlilng (#1764) 2022-02-10 13:03:02 -05:00
Micaiah Martin
9eed421c67 Updated Sed expression for Android manifests (#1763) 2022-02-10 09:42:57 -07:00
github-actions[bot]
15db96b06c Bumped version to 2.16.1 (#1762)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-10 08:19:02 -08:00
Joseph Flinn
54ccc1cbca Add iOS Share Extension to our version bump automation (#1761) 2022-02-10 07:54:43 -08:00
github-actions[bot]
ee69364b1d Bumped version to 2.16.0 (#1760)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-10 06:46:19 -08:00
Federico Maccaroni
76f1057951 Improved Autofocus code on ScanPage for better cancellation and exception handling #1228 (#1759) 2022-02-09 14:22:04 -03:00
Vincent Salucci
3491c1aaeb [Help] Update links to new pattern (#1758) 2022-02-08 17:42:53 -06:00
Oscar Hinton
427ff09af0 Client & Version headers (#1757) 2022-02-08 17:43:40 +01:00
github-actions[bot]
10fafaf8c8 Autosync the updated translations (#1752)
Co-authored-by: github-actions <>
2022-02-04 12:56:49 +01:00
Federico Maccaroni
31cdf401f1 Fix delete account SSO with CME that the OTP parameter was being sent incorrectly to the server (#1751) 2022-02-03 17:46:45 -03:00
Vincent Salucci
4373cee636 [Icons] Fast follower changes (#1750) 2022-02-03 10:34:20 -06:00
Federico Maccaroni
63b27f4e6d Removed punctuation on some string resources regarding send (#1747) 2022-02-02 16:07:58 -03:00
880 changed files with 82833 additions and 17079 deletions

18
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,18 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-format": {
"version": "5.1.250801",
"commands": [
"dotnet-format"
]
},
"cake.tool": {
"version": "2.2.0",
"commands": [
"dotnet-cake"
]
}
}
}

View File

@@ -6,6 +6,7 @@ root = true
# Don't use tabs for indentation. # Don't use tabs for indentation.
[*] [*]
indent_style = space indent_style = space
end_of_line = lf
# (Please don't specify an indent_size here; that has too many unintended consequences.) # (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files # Code files

2
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,2 @@
# .NET format https://github.com/bitwarden/mobile/pull/1738
04539af2a66668b6e85476d5cf318c9150ec4357

2
.gitattributes vendored
View File

@@ -1,7 +1,7 @@
############################################################################### ###############################################################################
# Set default behavior to automatically normalize line endings. # Set default behavior to automatically normalize line endings.
############################################################################### ###############################################################################
* text=auto * text=auto eol=lf
############################################################################### ###############################################################################
# Set default behavior for command prompt diff. # Set default behavior for command prompt diff.

View File

@@ -21,12 +21,8 @@
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required) - Please check for formatting errors (`dotnet format --verify-no-changes`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team) - Please add **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change has particular **deployment requirements** (notify the DevOps team) - If this change requires a **documentation update** - notify the documentation team
- If this change has particular **deployment requirements** - notify the DevOps team

22
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"schedule:monthly",
":maintainLockFilesMonthly",
":preserveSemverRanges",
":rebaseStalePrs",
":disableDependencyDashboard"
],
"enabledManagers": [
"nuget"
],
"packageRules": [
{
"matchManagers": ["nuget"],
"groupName": "Nuget updates",
"groupSlug": "nuget",
"separateMajorMinor": false
}
]
}

View File

@@ -14,6 +14,10 @@
<string>Dist: Extension 2021</string> <string>Dist: Extension 2021</string>
<key>com.8bit.bitwarden.share-extension</key> <key>com.8bit.bitwarden.share-extension</key>
<string>Dist: Share Extension 2021</string> <string>Dist: Share Extension 2021</string>
<key>com.8bit.bitwarden.watchkitapp</key>
<string>Dist: Bitwarden Watch App</string>
<key>com.8bit.bitwarden.watchkitapp.watchkitextension</key>
<string>Dist: Bitwarden Watch App Extension</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,64 @@
---
name: Automatic responses
on:
issues:
types:
- labeled
jobs:
close-issue:
name: 'Close issue with automatic response'
runs-on: ubuntu-20.04
permissions:
issues: write
steps:
# Feature request
- if: github.event.label.name == 'feature-request'
name: Feature request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
Please [sign up on our forums](https://community.bitwarden.com/signup) and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.
This issue will now be closed. Thanks!
# Intended behavior
- if: github.event.label.name == 'intended-behavior'
name: Intended behaviour
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request.
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
Please [sign up on our forums](https://community.bitwarden.com/signup) and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.
This issue will now be closed. Thanks!
# Customer support request
- if: github.event.label.name == 'customer-support'
name: Customer Support request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team.
Please contact us using our [Contact page](https://bitwarden.com/contact). You can include a link to this issue in the message content.
Alternatively, you can also search for an answer in our [help documentation](https://bitwarden.com/help/) or get help from other Bitwarden users on our [community forums](https://community.bitwarden.com/c/support/). The issue here will be closed.
# Resolved
- if: github.event.label.name == 'resolved'
name: Resolved
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
# Stale
- if: github.event.label.name == 'stale'
name: Stale
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
As we havent heard from you about this problem in some time, this issue will now be closed.
If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.

View File

@@ -4,8 +4,12 @@ name: Build
on: on:
push: push:
branches-ignore: branches-ignore:
- 'l10n_master' - "l10n_master"
- 'gh-pages' - "gh-pages"
paths-ignore:
- ".github/workflows/**"
workflow_dispatch:
inputs: {}
jobs: jobs:
cloc: cloc:
@@ -13,7 +17,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Set up CLOC - name: Set up CLOC
run: | run: |
@@ -32,33 +36,61 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }} hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Check if special branches exist - name: Check if special branches exist
id: branch-check id: branch-check
run: | run: |
if [[ $(git ls-remote --heads origin rc) ]]; then if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=rc_branch_exists::1" echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
else else
echo "::set-output name=rc_branch_exists::0" echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
fi fi
if [[ $(git ls-remote --heads origin hotfix) ]]; then if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
echo "::set-output name=hotfix_branch_exists::1" echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
else else
echo "::set-output name=hotfix_branch_exists::0" echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi fi
shell: bash shell: bash
android: android:
name: Android name: Android
runs-on: windows-2019 runs-on: windows-2022
needs: setup needs: setup
strategy:
fail-fast: false
matrix:
variant: ["prod", "qa"]
steps: steps:
- name: Set up MSBuild - name: Setup NuGet
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Work Around for broken Windows 2022 Runner Image
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
$InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
$componentsToAdd = @(
"Component.Xamarin"
)
[string]$workloadArgs = $componentsToAdd | ForEach-Object {" --add " + $_}
$Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"",$workloadArgs, '--quiet', '--norestart', '--nocache')
$process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
if ($process.ExitCode -eq 0)
{
Write-Host "components have been successfully added"
}
else
{
Write-Host "components were not installed"
exit 1
}
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
@@ -68,8 +100,9 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
with:
fetch-depth: 0
- name: Decrypt secrets - name: Decrypt secrets
env: env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }} DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
@@ -80,12 +113,17 @@ jobs:
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg --output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg --output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg --output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash shell: bash
- name: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }}
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash
- name: Increment version - name: Increment version
run: | run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER)) BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
@@ -101,30 +139,57 @@ jobs:
- name: Restore packages - name: Restore packages
run: nuget restore run: nuget restore
- name: Restore tools
run: dotnet tool restore
shell: pwsh
- name: Verify Format
run: dotnet tool run dotnet-format --check
shell: pwsh
- name: Run Core tests - name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx"
shell: pwsh
- name: Report test results
uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226
if: always()
with:
name: Test Results
path: "**/test-results.trx"
reporter: dotnet-trx
fail-on-error: true
- name: Build Play Store publisher - name: Build Play Store publisher
if: ${{ matrix.variant == 'prod' }}
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Build for Play Store - name: Setup Android build (${{ matrix.variant }})
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build Android
run: | run: |
$configuration = "Release"; $configuration = "Release";
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Build $configuration Configuration" Write-Output "##### Build $configuration Configuration"
Write-Output "########################################" Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration" msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
shell: pwsh shell: pwsh
- name: Sign for Play Store - name: Sign Android Build
env: env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }} PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: | run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj"); $androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration" Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################" Write-Output "########################################"
@@ -138,9 +203,8 @@ jobs:
Write-Output "##### Copy Google Play Bundle to project root" Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################" Write-Output "########################################"
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab"); $signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab"); $signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
Copy-Item $signedAabPath $signedAabDestPath Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################" Write-Output "########################################"
@@ -156,35 +220,43 @@ jobs:
Write-Output "##### Copy Release APK to project root" Write-Output "##### Copy Release APK to project root"
Write-Output "########################################" Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk"); $signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk"); $signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
Copy-Item $signedApkPath $signedApkDestPath Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh shell: pwsh
- name: Upload Prod .aab artifact
- name: Upload Play Store .aab artifact if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden.aab name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab path: ./com.x8bit.bitwarden.aab
if-no-files-found: error if-no-files-found: error
- name: Upload Play Store .apk artifact - name: Upload Prod .apk artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4 if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden.apk name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk path: ./com.x8bit.bitwarden.apk
if-no-files-found: error if-no-files-found: error
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
if-no-files-found: error
- name: Deploy to Play Store - name: Deploy to Play Store
if: | if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/master'
(github.ref == 'refs/heads/master' && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.hotfix_branch_exists == 0)
&& needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || github.ref == 'refs/heads/hotfix-rc' ) }}
|| github.ref == 'refs/heads/hotfix'
run: | run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll" PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json" CREDS_PATH="$HOME/secrets/play_creds.json"
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab" AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
TRACK="internal" TRACK="internal"
@@ -195,10 +267,35 @@ jobs:
f-droid: f-droid:
name: F-Droid Build name: F-Droid Build
runs-on: windows-2019 runs-on: windows-2022
steps: steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1 uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Work Around for broken Windows 2022 Runner Image
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
$InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
$componentsToAdd = @(
"Component.Xamarin"
)
[string]$workloadArgs = $componentsToAdd | ForEach-Object {" --add " + $_}
$Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"",$workloadArgs, '--quiet', '--norestart', '--nocache')
$process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
if ($process.ExitCode -eq 0)
{
Write-Host "components have been successfully added"
}
else
{
Write-Host "components were not installed"
exit 1
}
- name: Print environment - name: Print environment
run: | run: |
@@ -209,7 +306,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Decrypt secrets - name: Decrypt secrets
env: env:
@@ -237,6 +334,7 @@ jobs:
run: | run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj"); $androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj"); $appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml"); $androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
@@ -292,16 +390,16 @@ jobs:
$xml.Save($androidPath); $xml.Save($androidPath);
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj" Write-Output "##### Uninstall from Core.csproj"
Write-Output "########################################" Write-Output "########################################"
$xml=New-Object XML; $xml=New-Object XML;
$xml.Load($appPath); $xml.Load($corePath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']"); $appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode); $appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath); $xml.Save($corePath);
shell: pwsh shell: pwsh
- name: Restore packages - name: Restore packages
@@ -343,7 +441,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload F-Droid .apk artifact - name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk path: ./com.x8bit.bitwarden-fdroid.apk
@@ -352,9 +450,14 @@ jobs:
ios: ios:
name: Apple iOS name: Apple iOS
runs-on: macos-11 runs-on: macos-12
needs: setup needs: setup
steps: steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
@@ -364,7 +467,26 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
SECRETS: |
appcenter-ios-token
run: |
for i in ${SECRETS//,/ }
do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT
done
- name: Decrypt secrets - name: Decrypt secrets
env: env:
@@ -383,7 +505,16 @@ jobs:
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg --output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_share_extension.mobileprovision ./.github/secrets/dist_share_extension.mobileprovision.gpg --output $HOME/secrets/dist_share_extension.mobileprovision \
./.github/secrets/dist_share_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app.mobileprovision \
./.github/secrets/dist_watch_app.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app_extension.mobileprovision \
./.github/secrets/dist_watch_app_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/watchOS/bitwarden/GoogleService-Info.plist ./.github/secrets/GoogleService-Info.plist.gpg
shell: bash shell: bash
- name: Increment version - name: Increment version
@@ -398,6 +529,9 @@ jobs:
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
cd ../../..
shell: bash shell: bash
- name: Update Entitlements - name: Update Entitlements
@@ -432,6 +566,8 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH" mkdir -p "$PROFILES_DIR_PATH"
@@ -447,6 +583,25 @@ jobs:
SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}") SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision" cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision"
WATCH_APP_UUID=$(grep UUID -A1 -a $WATCH_APP_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_UUID.mobileprovision"
WATCH_APP_EXTENSION_UUID=$(grep UUID -A1 -a $WATCH_APP_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_EXTENSION_UUID.mobileprovision"
shell: bash
- name: Bulid WatchApp
run: |
echo "########################################"
echo "##### Build WatchApp with Release Configuration"
echo "########################################"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "########################################"
echo "##### Done"
echo "########################################"
shell: bash shell: bash
- name: Restore packages - name: Restore packages
@@ -479,20 +634,72 @@ jobs:
-exportOptionsPlist $EXPORT_OPTIONS_PATH -exportOptionsPlist $EXPORT_OPTIONS_PATH
shell: bash shell: bash
- name: Upload App Store .ipa artifact - name: Copy all dSYMs files to upload
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4 run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
EXPORT_PATH="./bitwarden-export"
WATCH_ARCHIVE_DSYMS_PATH="./src/watchOS/bitwarden.xcarchive/dSYMs/"
WATCH_DSYMS_EXPORT_PATH="$EXPORT_PATH/Watch_dSYMs"
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
mkdir $WATCH_DSYMS_EXPORT_PATH
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
shell: bash
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: Bitwarden.ipa name: Bitwarden iOS
path: ./bitwarden-export/Bitwarden.ipa path: |
./bitwarden-export/Bitwarden.ipa
./bitwarden-export/dSYMs/*.*
if-no-files-found: error if-no-files-found: error
- name: Install AppCenter CLI
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: npm install -g appcenter-cli
- name: Upload dSYMs to App Center
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
shell: bash
- name: Upload Watch dSYMs to Firebase Crashlytics
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: |
echo "########################################"
echo "##### Uploading Watch dSYMs to Firebase"
echo "########################################"
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \;
shell: bash
- name: Deploy to App Store - name: Deploy to App Store
if: | if: |
(github.ref == 'refs/heads/master' (github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }} APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
@@ -514,22 +721,29 @@ jobs:
_CROWDIN_PROJECT_ID: "269690" _CROWDIN_PROJECT_ID: "269690"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Login to Azure - name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403 env:
with: KEYVAULT: bitwarden-prod-kv
keyvault: "bitwarden-prod-kv" SECRETS: |
secrets: "crowdin-api-token" crowdin-api-token
run: |
for i in ${SECRETS//,/ }
do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT
done
- name: Upload Sources - name: Upload Sources
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2 uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -553,9 +767,9 @@ jobs:
steps: steps:
- name: Check if any job failed - name: Check if any job failed
if: | if: |
(github.ref == 'refs/heads/master') (github.ref == 'refs/heads/master')
|| (github.ref == 'refs/heads/rc') || (github.ref == 'refs/heads/rc')
|| (github.ref == 'refs/heads/hotfix') || (github.ref == 'refs/heads/hotfix-rc')
env: env:
CLOC_STATUS: ${{ needs.cloc.result }} CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }} ANDROID_STATUS: ${{ needs.android.result }}
@@ -576,21 +790,28 @@ jobs:
fi fi
- name: Login to Azure - Prod Subscription - name: Login to Azure - Prod Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
if: failure() if: failure()
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure() if: failure()
with: env:
keyvault: "bitwarden-prod-kv" KEYVAULT: bitwarden-prod-kv
secrets: "devops-alerts-slack-webhook-url" SECRETS: |
devops-alerts-slack-webhook-url
run: |
for i in ${SECRETS//,/ }
do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT
done
- name: Notify Slack on failure - name: Notify Slack on failure
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2 uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
if: failure() if: failure()
env: env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -24,13 +24,13 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403 uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with: with:
keyvault: "bitwarden-prod-kv" keyvault: "bitwarden-prod-kv"
secrets: "crowdin-api-token" secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations - name: Download translations
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -40,10 +40,12 @@ jobs:
upload_sources: false upload_sources: false
upload_translations: false upload_translations: false
download_translations: true download_translations: true
github_user_name: "github-actions" github_user_name: "bitwarden-devops-bot"
github_user_email: "<>" github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
commit_message: "Autosync the updated translations" commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync localization_branch_name: crowdin-auto-sync
create_pull_request: true create_pull_request: true
pull_request_title: "Autosync Crowdin Translations" pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated 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 }}

16
.github/workflows/enforce-labels.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
---
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with:
BANNED_LABELS: "hold,needs-qa"
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"

View File

@@ -1,5 +1,6 @@
--- ---
name: Release name: Release
run-name: Release ${{ inputs.release_type }}
on: on:
workflow_dispatch: workflow_dispatch:
@@ -12,6 +13,12 @@ on:
options: options:
- Initial Release - Initial Release
- Redeploy - Redeploy
- Dry Run
fdroid_publish:
description: 'Publish to f-droid store'
required: true
default: true
type: boolean
jobs: jobs:
release: release:
@@ -21,86 +28,124 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }} branch-name: ${{ steps.branch.outputs.branch-name }}
steps: steps:
- name: Branch check - name: Branch check
if: github.event.inputs.release_type != 'Dry Run'
run: | run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "===================================" echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix' branches" echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "===================================" echo "==================================="
exit 1 exit 1
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Retrieve Mobile release version - name: Check Release Version
id: retrieve-mobile-version id: version
run: | uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
ver=$(sed -n -e '/android:versionName/ s/.*\= *//p' ./src/Android/Properties/AndroidManifest.xml | tr -d '"') with:
echo "::set-output name=mobile_version::${ver}" release-type: ${{ github.event.inputs.release_type }}
shell: bash project-type: xamarin
file: src/Android/Properties/AndroidManifest.xml
- name: Check to make sure Mobile release version has been bumped
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_ver=$(hub release -L 1 -f '%T')
latest_ver=${latest_ver:1}
echo "Latest version: $latest_ver"
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
echo "Version: $ver"
if [ "$latest_ver" = "$ver" ]; then
echo "Version has not been bumped!"
exit 1
fi
shell: bash
- name: Get branch name - name: Get branch name
id: branch id: branch
run: | run: |
BRANCH_NAME=$(basename ${{ github.ref }}) BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME" echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create GitHub deployment
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
id: deployment
with:
token: '${{ secrets.GITHUB_TOKEN }}'
initial-status: 'in_progress'
environment: 'production'
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
task: release
- name: Download all artifacts - name: Download all artifacts
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0 if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }} branch: ${{ steps.branch.outputs.branch-name }}
- name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
branch: master
- name: Prep Bitwarden iOS release asset
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release - name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5 if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
with: with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk, ./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk, ./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden.ipa/Bitwarden.ipa" ./Bitwarden iOS.zip"
commit: ${{ github.sha }} commit: ${{ github.sha }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }} tag: v${{ steps.version.outputs.version }}
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }} name: Version ${{ steps.version.outputs.version }}
body: "<insert release notes here>" body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
draft: true draft: true
- name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
f-droid: f-droid:
name: F-Droid Release name: F-Droid Release
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: release needs: release
if: inputs.fdroid_publish
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download F-Droid .apk artifact - name: Download F-Droid .apk artifact
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0 if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ needs.release.outputs.branch-name }} branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
- name: Dry Run - Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
branch: master
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.3.0 uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
with: with:
node-version: '10.x' node-version: '10.x'
@@ -164,4 +209,5 @@ jobs:
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages - name: Deploy to gh-pages
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: npm run deploy run: npm run deploy

30
.github/workflows/stale-bot.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: 'Close stale issues and PRs'
on:
workflow_dispatch:
schedule: # Run once a day at 5.23am (arbitrary but should avoid peak loads on the hour)
- cron: '23 5 * * *'
jobs:
stale:
name: 'Check for stale issues and PRs'
runs-on: ubuntu-20.04
steps:
- name: 'Run stale action'
uses: actions/stale@3cc123766321e9f15a6676375c154ccffb12a358 # v5.0.0
with:
stale-issue-label: 'needs-reply'
stale-pr-label: 'needs-changes'
days-before-stale: -1 # Do not apply the stale labels automatically, this is a manual process
days-before-issue-close: 14 # Close issue if no further activity after X days
days-before-pr-close: 21 # Close PR if no further activity after X days
close-issue-message: |
We need more information before we can help you with your problem. As we havent heard from you recently, this issue will be closed.
If this happens again or continues to be an problem, please respond to this issue with the information weve requested and anything else relevant.
close-pr-message: |
We cant merge your pull request until you make the changes weve requested. As we havent heard from you recently, this pull request will be closed.
If youre still working on this, please respond here after youve made the changes weve requested and our team will re-open it for further review.
Please make sure to resolve any conflicts with the master branch before requesting another review.

63
.github/workflows/version-auto-bump.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
---
name: Version Auto Bump
on:
push:
tags:
- v**
jobs:
setup:
name: "Setup"
runs-on: ubuntu-22.04
outputs:
version_number: ${{ steps.version.outputs.new-version }}
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Calculate bumped version
id: version
env:
RELEASE_TAG: ${{ github.ref }}
run: |
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
echo "Current Major: $CURR_MAJOR"
echo "Current Patch: $CURR_PATCH"
NEW_PATCH=$((CURR_PATCH+1))
NEW_VER=$CURR_MAJOR.$NEW_PATCH
echo "New Version: $NEW_VER"
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
name: "Trigger version bump workflow"
runs-on: ubuntu-22.04
needs:
- setup
steps:
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Call GitHub API to trigger workflow bump
env:
TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
VERSION: ${{ needs.setup.outputs.version_number}}
run: |
JSON_STRING=$(printf '{"ref":"master", "inputs": { "version_number":"%s"}}' "$VERSION")
curl \
-X POST \
-i -u bitwarden-devops-bot:$TOKEN \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/bitwarden/mobile/actions/workflows/version-bump.yml/dispatches \
-d $JSON_STRING

View File

@@ -16,15 +16,28 @@ jobs:
- name: Checkout Branch - name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch - name: Login to Azure - Prod Subscription
run: | uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with: with:
ref: version_bump_${{ github.event.inputs.version_number }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@c8bb57c57e8df1be8c73ff3d59deab1dbc00e0d1
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Create Version Branch
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Android XML - name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945 uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
@@ -44,22 +57,43 @@ jobs:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Extension/Info.plist" file_path: "./src/iOS.Extension/Info.plist"
- name: Bump Version - iOS.ShareExtension
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.ShareExtension/Info.plist"
- name: Bump Version - iOS - name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945 uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS/Info.plist" file_path: "./src/iOS/Info.plist"
- name: Commit files - name: Setup git
run: | run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "github-actions[bot]" git config --local user.name "bitwarden-devops-bot"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Check if version changed
id: version-changed
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT
else
echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT
echo "No changes to commit!";
fi
- name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes - name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git push -u origin version_bump_${{ github.event.inputs.version_number }} run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR - name: Create Version PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env: env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}" PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

11
.github/workflows/workflow-linter.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: Workflow Linter
on:
pull_request:
paths:
- .github/workflows/**
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master

129
.gitignore vendored
View File

@@ -30,6 +30,7 @@ Components/
[Rr]eleases/ [Rr]eleases/
x64/ x64/
x86/ x86/
!src/lib/x86/
build/ build/
bld/ bld/
[Bb]in/ [Bb]in/
@@ -208,4 +209,130 @@ FakesAssemblies/
# Other # Other
project.lock.json project.lock.json
.DS_Store .DS_Store
src/App/Css src/App/Css
tools
# Created by https://www.toptal.com/developers/gitignore/api/swift,objective-c
# Edit at https://www.toptal.com/developers/gitignore?templates=swift,objective-c
### Objective-C ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### Objective-C Patch ###
### Swift ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
# Accio dependency management
Dependencies/
.accio/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
# End of https://www.toptal.com/developers/gitignore/api/swift,objective-c

View File

@@ -1,40 +1,3 @@
# How to Contribute # How to Contribute
Contributions of all kinds are welcome! Our [Contributing Guidelines](https://contributing.bitwarden.com/contributing/) are located in our [Contributing Documentation](https://contributing.bitwarden.com/). The documentation also includes recommended tooling, code style tips, and lots of other great information to get you started.
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (i10n) section below
## Contributor Agreement
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/mobile) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
## Pull Request Guidelines
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
# Localization (l10n)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile)
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@@ -12,20 +12,26 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
# Build/Run # Build/Run
**Requirements** Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/clients/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
- [Visual Studio](https://visualstudio.microsoft.com/) # We're Hiring!
- [Xamarin](https://docs.microsoft.com/en-us/xamarin/get-started/installation/?pivots=windows)
**Run the app** Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
- Open the solution file in Visual Studio.
- Restore the nuget packages.
- Build and run the app.
# Contribute # Contribute
Code contributions are welcome! Visual Studio with Xamarin is required to work on this project. Please commit any pull requests against the `master` branch. Code contributions are welcome! Please commit any pull requests against the `master` 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.
Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
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. 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.
### Dotnet-format
We recently migrated to using dotnet-format as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35`
3. Resolve any merge conflicts, commit.
4. Run `dotnet tool run dotnet-format`
5. Commit
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
7. Push

View File

@@ -1,39 +1,11 @@
Bitwarden believes that working with security researchers across the globe is crucial to keeping our 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!
users safe. If you believe you've found a security issue in our product or service, we encourage you to
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
# Disclosure Policy # Disclosure Policy
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every - 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.
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.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a - 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.
third-party. We may publicly disclose the issue before resolving it, if appropriate. - If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
- 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).
# In-scope
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
code is available at https://github.com/bitwarden.
# Exclusions
The following bug classes are out-of scope:
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
or that we already know of. Note that some of our issue tracking is private.
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
upstream maintainer.
- Attacks requiring physical access to a user's device.
- Self-XSS
- Issues related to software or protocols not under Bitwarden's control
- Vulnerabilities in outdated versions of Bitwarden
- Missing security best practices that do not directly lead to a vulnerability
- Issues that do not have any impact on the general public
While researching, we'd like to ask you to refrain from: While researching, we'd like to ask you to refrain from:
@@ -42,4 +14,8 @@ While researching, we'd like to ask you to refrain from:
- Social engineering (including phishing) of Bitwarden staff or contractors - Social engineering (including phishing) of Bitwarden staff or contractors
- Any physical attempts against Bitwarden property or data centers - 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.
Thank you for helping keep Bitwarden and our users safe! Thank you for helping keep Bitwarden and our users safe!

346
build.cake Normal file
View File

@@ -0,0 +1,346 @@
#addin nuget:?package=Cake.FileHelpers&version=5.0.0
#addin nuget:?package=Cake.AndroidAppManifest&version=1.1.2
#addin nuget:?package=Cake.Plist&version=0.7.0
#addin nuget:?package=Cake.Incubator&version=7.0.0
#tool dotnet:?package=GitVersion.Tool&version=5.10.3
using Path = System.IO.Path;
var debugScript = Argument<bool>("debugScript", false);
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var variant = Argument("variant", "dev");
abstract record VariantConfig(
string AppName,
string AndroidPackageName,
string iOSBundleId,
string ApsEnvironment
);
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development");
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development");
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production");
VariantConfig GetVariant() => variant.ToLower() switch{
"qa" => new QA(),
"beta" => new Beta(),
"prod" => new Prod(),
_ => new Dev()
};
GitVersion _gitVersion; //will be set by GetGitInfo task
var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this
string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task
string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}";
string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git);
int CreateBuildNumber(int previousNumber) => ++previousNumber;
Task("GetGitInfo")
.Does(()=> {
_gitVersion = GitVersion(new GitVersionSettings());
if(debugScript)
{
Information($"GitVersion Dump:\n{_gitVersion.Dump()}");
}
Information("Git data Load successfully.");
});
#region Android
Task("UpdateAndroidAppIcon")
.Does(()=>{
//TODO we'll implement variant icons later
//manifest.ApplicationIcon = "@mipmap/ic_launcher";
Information($"Updated Androix App Icon with success");
});
Task("UpdateAndroidManifest")
.IsDependentOn("GetGitInfo")
.Does(()=>
{
var buildVariant = GetVariant();
var manifestPath = Path.Combine(_slnPath, "src", "Android", "Properties", "AndroidManifest.xml");
// Cake.AndroidAppManifest doesn't currently enable us to access nested items so, quick (not ideal) fix:
var manifestText = FileReadText(manifestPath);
manifestText = manifestText.Replace("com.x8bit.bitwarden.", buildVariant.AndroidPackageName + ".");
manifestText = manifestText.Replace("android:label=\"Bitwarden\"", $"android:label=\"{buildVariant.AppName}\"");
FileWriteText(manifestPath, manifestText);
var manifest = DeserializeAppManifest(manifestPath);
var prevVersionCode = manifest.VersionCode;
var prevVersionName = manifest.VersionName;
_androidPackageName = manifest.PackageName;
//manifest.VersionCode = CreateBuildNumber(prevVersionCode);
manifest.VersionName = GetVersionName(prevVersionName, buildVariant, _gitVersion);
manifest.PackageName = buildVariant.AndroidPackageName;
manifest.ApplicationLabel = buildVariant.AppName;
//Information($"AndroidManigest.xml VersionCode from {prevVersionCode} to {manifest.VersionCode}");
Information($"AndroidManigest.xml VersionName from {prevVersionName} to {manifest.VersionName}");
Information($"AndroidManigest.xml PackageName from {_androidPackageName} to {buildVariant.AndroidPackageName}");
Information($"AndroidManigest.xml ApplicationLabel to {buildVariant.AppName}");
SerializeAppManifest(manifestPath, manifest);
Information("AndroidManifest updated with success!");
});
void ReplaceInFile(string filePath, string oldtext, string newtext)
{
var fileText = FileReadText(filePath);
if(string.IsNullOrEmpty(fileText) || !fileText.Contains(oldtext))
{
throw new Exception($"Couldn't find {filePath} or it didn't contain: {oldtext}");
}
fileText = fileText.Replace(oldtext, newtext);
FileWriteText(filePath, fileText);
Information($"{filePath} modified successfully.");
}
Task("UpdateAndroidCodeFiles")
.IsDependentOn("UpdateAndroidManifest")
.Does(()=> {
var buildVariant = GetVariant();
//We're not using _androidPackageName here because the codefile is currently slightly different string than the one in AndroidManifest.xml
var keyName = "com.8bit.bitwarden";
var fixedPackageName = buildVariant.AndroidPackageName.Replace("x8bit", "8bit");
var filePath = Path.Combine(_slnPath, "src", "Android", "Services", "BiometricService.cs");
ReplaceInFile(filePath, keyName, fixedPackageName);
var packageFileList = new string[] {
Path.Combine(_slnPath, "src", "Android", "MainActivity.cs"),
Path.Combine(_slnPath, "src", "Android", "MainApplication.cs"),
Path.Combine(_slnPath, "src", "Android", "Constants.cs"),
Path.Combine(_slnPath, "src", "Android", "Accessibility", "AccessibilityService.cs"),
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillHelpers.cs"),
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "ClearClipboardAlarmReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "EventUploadReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "PackageReplacedReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "RestrictionsChangedReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Services", "DeviceActionService.cs"),
Path.Combine(_slnPath, "src", "Android", "Services", "FileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "AutofillTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "GeneratorTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "MyVaultTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "google-services.json"),
Path.Combine(_slnPath, "store", "google", "Publisher", "Program.cs"),
};
foreach(string path in packageFileList)
{
ReplaceInFile(path, "com.x8bit.bitwarden", buildVariant.AndroidPackageName);
}
var labelFileList = new string[] {
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
};
foreach(string path in labelFileList)
{
ReplaceInFile(path, "Bitwarden\"", $"{buildVariant.AppName}\"");
}
});
#endregion Android
#region iOS
enum iOSProjectType
{
Null,
MainApp,
Autofill,
Extension,
ShareExtension
}
string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
{
iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill",
iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension",
iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension",
_ => buildVariant.iOSBundleId
};
string GetiOSBundleName(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
{
iOSProjectType.Autofill => $"{buildVariant.AppName} Autofill",
iOSProjectType.Extension => $"{buildVariant.AppName} Extension",
iOSProjectType.ShareExtension => $"{buildVariant.AppName} Share Extension",
_ => buildVariant.AppName
};
private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, GitVersion git, iOSProjectType projectType = iOSProjectType.MainApp)
{
var plistFile = File(plistPath);
dynamic plist = DeserializePlist(plistFile);
var prevVersionName = plist["CFBundleShortVersionString"];
var prevVersionString = plist["CFBundleVersion"];
var prevVersion = int.Parse(plist["CFBundleVersion"]);
var prevBundleId = plist["CFBundleIdentifier"];
var prevBundleName = plist["CFBundleName"];
//var newVersion = CreateBuildNumber(prevVersion).ToString();
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
var newBundleId = GetiOSBundleId(buildVariant, projectType);
var newBundleName = GetiOSBundleName(buildVariant, projectType);
plist["CFBundleName"] = newBundleName;
plist["CFBundleDisplayName"] = newBundleName;
//plist["CFBundleVersion"] = newVersion;
plist["CFBundleShortVersionString"] = newVersionName;
plist["CFBundleIdentifier"] = newBundleId;
if(projectType == iOSProjectType.MainApp)
{
plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url";
}
if(projectType == iOSProjectType.Extension)
{
var keyText = plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"];
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
}
SerializePlist(plistFile, plist);
Information($"Changed app name from {prevBundleName} to {newBundleName}");
//Information($"Changed Bundle Version from {prevVersion} to {newVersion}");
Information($"Changed Bundle Short Version name from {prevVersionName} to {newVersionName}");
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
Information($"{plistPath} updated with success!");
}
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
{
var EntitlementlistFile = File(entitlementsPath);
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };;
Information($"Changed ApsEnvironment name to {buildVariant.ApsEnvironment}");
Information($"Changed keychain-access-groups bundleID to {buildVariant.iOSBundleId}");
SerializePlist(EntitlementlistFile, Entitlements);
Information($"{entitlementsPath} updated with success!");
}
Task("UpdateiOSIcon")
.Does(()=>{
//TODO we'll implement variant icons later
Information($"Updating IOS App Icon");
});
Task("UpdateiOSPlist")
.IsDependentOn("GetGitInfo")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSAutofillPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSExtensionPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSShareExtensionPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSCodeFiles")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var fileList = new string[] {
Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"),
Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"),
Path.Combine(".github", "resources", "export-options-ad-hoc.plist"),
Path.Combine(".github", "resources", "export-options-app-store.plist"),
};
foreach(string path in fileList)
{
ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId);
}
});
#endregion iOS
#region Main Tasks
Task("Android")
//.IsDependentOn("UpdateAndroidAppIcon")
.IsDependentOn("UpdateAndroidManifest")
.IsDependentOn("UpdateAndroidCodeFiles")
.Does(()=>
{
Information("Android app updated");
});
Task("iOS")
//.IsDependentOn("UpdateiOSIcon")
.IsDependentOn("UpdateiOSPlist")
.IsDependentOn("UpdateiOSAutofillPlist")
.IsDependentOn("UpdateiOSExtensionPlist")
.IsDependentOn("UpdateiOSShareExtensionPlist")
.IsDependentOn("UpdateiOSCodeFiles")
.Does(()=>
{
Information("iOS app updated");
});
Task("Default")
.Does(() => {
var usage = @"Missing target.
Usage:
dotnet cake build.cake --target (Android | iOS) --variant (dev | qa | beta | prod)
Options:
--debugScript=<bool> Script debug mode.
";
Information(usage);
});
#endregion Main Tasks
RunTarget(target);

BIN
lib/ios/libargon2.a Normal file

Binary file not shown.

View File

@@ -31,6 +31,9 @@ namespace Bit.Droid.Accessibility
// So keep them in sync with: // So keep them in sync with:
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers} // - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
// - Resources/xml/autofillservice.xml // - Resources/xml/autofillservice.xml
new Browser("alook.browser", "search_fragment_input_view"),
new Browser("alook.browser.google", "search_fragment_input_view"),
new Browser("app.vanadium.browser", "url_bar"),
new Browser("com.amazon.cloud9", "url"), new Browser("com.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"), new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"), new Browser("com.android.chrome", "url_bar"),
@@ -52,6 +55,7 @@ namespace Bit.Droid.Accessibility
new Browser("com.google.android.apps.chrome", "url_bar"), new Browser("com.google.android.apps.chrome", "url_bar"),
new Browser("com.google.android.apps.chrome_dev", "url_bar"), new Browser("com.google.android.apps.chrome_dev", "url_bar"),
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId. // Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
new Browser("com.iode.firefox", "mozac_browser_toolbar_url_view"),
new Browser("com.jamal2367.styx", "search"), new Browser("com.jamal2367.styx", "search"),
new Browser("com.kiwibrowser.browser", "url_bar"), new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.kiwibrowser.browser.dev", "url_bar"), new Browser("com.kiwibrowser.browser.dev", "url_bar"),
@@ -63,13 +67,16 @@ namespace Bit.Droid.Accessibility
new Browser("com.mmbox.xbrowser", "search_box"), new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.mycompany.app.soulbrowser", "edit_text"), new Browser("com.mycompany.app.soulbrowser", "edit_text"),
new Browser("com.naver.whale", "url_bar"), new Browser("com.naver.whale", "url_bar"),
new Browser("com.neeva.app", "full_url_text_view"),
new Browser("com.opera.browser", "url_field"), new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"), new Browser("com.opera.browser.beta", "url_field"),
new Browser("com.opera.gx", "addressbarEdit"),
new Browser("com.opera.mini.native", "url_field"), new Browser("com.opera.mini.native", "url_field"),
new Browser("com.opera.mini.native.beta", "url_field"), new Browser("com.opera.mini.native.beta", "url_field"),
new Browser("com.opera.touch", "addressbarEdit"), new Browser("com.opera.touch", "addressbarEdit"),
new Browser("com.qflair.browserq", "url"), new Browser("com.qflair.browserq", "url"),
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4) new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
new Browser("com.rainsee.create", "search_box"),
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"), new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"), new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
new Browser("com.stoutner.privacybrowser.free", "url_edittext"), new Browser("com.stoutner.privacybrowser.free", "url_edittext"),
@@ -79,6 +86,9 @@ namespace Bit.Droid.Accessibility
new Browser("com.vivaldi.browser.sopranos", "url_bar"), new Browser("com.vivaldi.browser.sopranos", "url_bar"),
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title", new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title",
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0) (s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
new Browser("com.yjllq.internet", "search_box"),
new Browser("com.yjllq.kito", "search_box"),
new Browser("com.yujian.ResideMenuDemo", "search_box"),
new Browser("com.z28j.feel", "g2"), new Browser("com.z28j.feel", "g2"),
new Browser("idm.internet.download.manager", "search"), new Browser("idm.internet.download.manager", "search"),
new Browser("idm.internet.download.manager.adm.lite", "search"), new Browser("idm.internet.download.manager.adm.lite", "search"),
@@ -86,6 +96,7 @@ namespace Bit.Droid.Accessibility
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"), new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "am,an"), new Browser("mark.via", "am,an"),
new Browser("mark.via.gp", "as"), new Browser("mark.via.gp", "as"),
new Browser("net.dezor.browser", "url_bar"),
new Browser("net.slions.fulguris.full.download", "search"), new Browser("net.slions.fulguris.full.download", "search"),
new Browser("net.slions.fulguris.full.download.debug", "search"), new Browser("net.slions.fulguris.full.download.debug", "search"),
new Browser("net.slions.fulguris.full.playstore", "search"), new Browser("net.slions.fulguris.full.playstore", "search"),
@@ -125,6 +136,7 @@ namespace Bit.Droid.Accessibility
new Browser("com.htc.sense.browser", "title"), new Browser("com.htc.sense.browser", "title"),
new Browser("com.jerky.browser2", "enterUrl"), new Browser("com.jerky.browser2", "enterUrl"),
new Browser("com.ksmobile.cb", "address_bar_edit_text"), new Browser("com.ksmobile.cb", "address_bar_edit_text"),
new Browser("com.lemurbrowser.exts","url_bar"),
new Browser("com.linkbubble.playstore", "url_text"), new Browser("com.linkbubble.playstore", "url_text"),
new Browser("com.mx.browser", "address_editor_with_progress"), new Browser("com.mx.browser", "address_editor_with_progress"),
new Browser("com.mx.browser.tablet", "address_editor_with_progress"), new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
@@ -363,7 +375,7 @@ namespace Bit.Droid.Accessibility
public static string GetUri(AccessibilityNodeInfo root) public static string GetUri(AccessibilityNodeInfo root)
{ {
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName); var uri = string.Concat(Core.Constants.AndroidAppProtocol, root.PackageName);
if (SupportedBrowsers.ContainsKey(root.PackageName)) if (SupportedBrowsers.ContainsKey(root.PackageName))
{ {
var browser = SupportedBrowsers[root.PackageName]; var browser = SupportedBrowsers[root.PackageName];

View File

@@ -10,13 +10,12 @@ using Android.Views;
using Android.Views.Accessibility; using Android.Views.Accessibility;
using Android.Widget; using Android.Widget;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Droid.Accessibility namespace Bit.Droid.Accessibility
{ {
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")] [Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })] [IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")] [MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")] [Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
@@ -25,7 +24,7 @@ namespace Bit.Droid.Accessibility
private const string BitwardenPackage = "com.x8bit.bitwarden"; private const string BitwardenPackage = "com.x8bit.bitwarden";
private const string BitwardenWebsite = "vault.bitwarden.com"; private const string BitwardenWebsite = "vault.bitwarden.com";
private IStorageService _storageService; private IStateService _stateService;
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
private DateTime? _lastSettingsReload = null; private DateTime? _lastSettingsReload = null;
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1); private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
@@ -444,9 +443,9 @@ namespace Bit.Droid.Accessibility
private void LoadServices() private void LoadServices()
{ {
if (_storageService == null) if (_stateService == null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
} }
if (_broadcasterService == null) if (_broadcasterService == null)
{ {
@@ -460,12 +459,12 @@ namespace Bit.Droid.Accessibility
if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan) if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
{ {
_lastSettingsReload = now; _lastSettingsReload = now;
var uris = await _storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey); var uris = await _stateService.GetAutofillBlacklistedUrisAsync();
if (uris != null) if (uris != null)
{ {
_blacklistedUris = new HashSet<string>(uris); _blacklistedUris = new HashSet<string>(uris);
} }
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded); var isAutoFillTileAdded = await _stateService.GetAutofillTileAddedAsync();
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault(); AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
} }
} }

View File

@@ -15,7 +15,7 @@
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix> <MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix> <MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion> <TargetFrameworkVersion>v13.0</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
@@ -75,24 +75,24 @@
<Version>2.1.0.4</Version> <Version>2.1.0.4</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Portable.BouncyCastle"> <PackageReference Include="Portable.BouncyCastle">
<Version>1.8.10</Version> <Version>1.9.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" /> <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1.1" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.9" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.14" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.11" /> <PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.17" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.10" /> <PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0.1" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" /> <PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.15" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" /> <PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1.1" />
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging"> <PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version> <Version>123.0.8</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0.4" /> <PackageReference Include="Xamarin.Google.Android.Material" Version="1.6.1.1" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.37.0" /> <PackageReference Include="Xamarin.Google.Dagger" Version="2.41.0.2" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet"> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>117.0.1</Version> <Version>118.0.1.2</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -103,8 +103,10 @@
<Compile Include="Accessibility\Browser.cs" /> <Compile Include="Accessibility\Browser.cs" />
<Compile Include="Accessibility\NodeList.cs" /> <Compile Include="Accessibility\NodeList.cs" />
<Compile Include="Accessibility\KnownUsernameField.cs" /> <Compile Include="Accessibility\KnownUsernameField.cs" />
<Compile Include="Autofill\AutofillConstants.cs" />
<Compile Include="Autofill\AutofillHelpers.cs" /> <Compile Include="Autofill\AutofillHelpers.cs" />
<Compile Include="Autofill\AutofillService.cs" /> <Compile Include="Autofill\AutofillService.cs" />
<Compile Include="Autofill\AutofillExternalSelectionActivity.cs" />
<Compile Include="Autofill\Field.cs" /> <Compile Include="Autofill\Field.cs" />
<Compile Include="Autofill\FieldCollection.cs" /> <Compile Include="Autofill\FieldCollection.cs" />
<Compile Include="Autofill\FilledItem.cs" /> <Compile Include="Autofill\FilledItem.cs" />
@@ -145,12 +147,19 @@
<Compile Include="Tiles\GeneratorTileService.cs" /> <Compile Include="Tiles\GeneratorTileService.cs" />
<Compile Include="Tiles\MyVaultTileService.cs" /> <Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" /> <Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" /> <Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" /> <Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" /> <Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" /> <Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\IntentExtensions.cs" /> <Compile Include="Utilities\IntentExtensions.cs" />
<Compile Include="Renderers\CustomPageRenderer.cs" />
<Compile Include="Effects\NoEmojiKeyboardEffect.cs" />
<Compile Include="Receivers\NotificationDismissReceiver.cs" />
<Compile Include="Services\FileService.cs" />
<Compile Include="Services\AutofillHandler.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
<Compile Include="Services\WatchDeviceService.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" /> <AndroidAsset Include="Assets\bwi-font.ttf" />
@@ -160,6 +169,10 @@
<GoogleServicesJson Include="google-services.json" /> <GoogleServicesJson Include="google-services.json" />
<GoogleServicesJson Include="google-services.json.enc" /> <GoogleServicesJson Include="google-services.json.enc" />
<None Include="fdroid-keystore.jks.enc" /> <None Include="fdroid-keystore.jks.enc" />
<AndroidNativeLibrary Include="lib\arm64-v8a\libargon2.so" />
<AndroidNativeLibrary Include="lib\armeabi-v7a\libargon2.so" />
<AndroidNativeLibrary Include="lib\x86\libargon2.so" />
<AndroidNativeLibrary Include="lib\x86_64\libargon2.so" />
<None Include="Properties\AndroidManifest.xml" /> <None Include="Properties\AndroidManifest.xml" />
<None Include="upload-keystore.jks.enc" /> <None Include="upload-keystore.jks.enc" />
</ItemGroup> </ItemGroup>
@@ -171,9 +184,11 @@
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" /> <AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" /> <AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" /> <AndroidResource Include="Resources\drawable\card.xml" />
<AndroidResource Include="Resources\drawable\cog.xml" /> <AndroidResource Include="Resources\drawable\cog_environment.xml" />
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
<AndroidResource Include="Resources\drawable\icon.xml" /> <AndroidResource Include="Resources\drawable\icon.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" /> <AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_monochrome.xml" />
<AndroidResource Include="Resources\drawable\ic_warning.xml" /> <AndroidResource Include="Resources\drawable\ic_warning.xml" />
<AndroidResource Include="Resources\drawable\id.xml" /> <AndroidResource Include="Resources\drawable\id.xml" />
<AndroidResource Include="Resources\drawable\info.xml" /> <AndroidResource Include="Resources\drawable\info.xml" />
@@ -211,6 +226,13 @@
<AndroidResource Include="Resources\values\colors.xml" /> <AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\values\manifest.xml" /> <AndroidResource Include="Resources\values\manifest.xml" />
<AndroidResource Include="Resources\values-v30\manifest.xml" /> <AndroidResource Include="Resources\values-v30\manifest.xml" />
<AndroidResource Include="Resources\drawable-v26\splash_screen_round.xml" />
<AndroidResource Include="Resources\drawable\logo_rounded.xml" />
<AndroidResource Include="Resources\drawable-night-v26\splash_screen_round.xml" />
<AndroidResource Include="Resources\drawable\ic_notification.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" /> <AndroidResource Include="Resources\drawable\splash_screen.xml" />
@@ -278,6 +300,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\values-v30\" /> <Folder Include="Resources\values-v30\" />
<Folder Include="Resources\drawable-v26\" />
<Folder Include="Resources\drawable-night-v26\" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

Binary file not shown.

View File

@@ -0,0 +1,10 @@
namespace Bit.Droid.Autofill
{
public class AutofillConstants
{
public const string AutofillFramework = "autofillFramework";
public const string AutofillFrameworkFillType = "autofillFrameworkFillType";
public const string AutofillFrameworkUri = "autofillFrameworkUri";
public const string AutofillFrameworkCipherId = "autofillFrameworkCipherId";
}
}

View File

@@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
public class AutofillExternalSelectionActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
var cipherId = Intent?.GetStringExtra(AutofillConstants.AutofillFrameworkCipherId);
if (string.IsNullOrEmpty(cipherId))
{
SetResult(Result.Canceled);
Finish();
return;
}
GetCipherAndPerformAutofillAsync(cipherId).FireAndForget();
}
private async Task GetCipherAndPerformAutofillAsync(string cipherId)
{
var cipherService = ServiceContainer.Resolve<ICipherService>();
var cipher = await cipherService.GetAsync(cipherId);
var decCipher = await cipher.DecryptAsync();
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
autofillHandler.Autofill(decCipher);
}
}
}

View File

@@ -19,6 +19,7 @@ using AndroidX.AutoFill.Inline;
using AndroidX.AutoFill.Inline.V1; using AndroidX.AutoFill.Inline.V1;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using SaveFlags = Android.Service.Autofill.SaveFlags; using SaveFlags = Android.Service.Autofill.SaveFlags;
using Bit.Droid.Utilities;
namespace Bit.Droid.Autofill namespace Bit.Droid.Autofill
{ {
@@ -51,6 +52,9 @@ namespace Bit.Droid.Autofill
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too. // - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
public static HashSet<string> CompatBrowsers = new HashSet<string> public static HashSet<string> CompatBrowsers = new HashSet<string>
{ {
"alook.browser",
"alook.browser.google",
"app.vanadium.browser",
"com.amazon.cloud9", "com.amazon.cloud9",
"com.android.browser", "com.android.browser",
"com.android.chrome", "com.android.chrome",
@@ -71,9 +75,11 @@ namespace Bit.Droid.Autofill
"com.google.android.apps.chrome", "com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev", "com.google.android.apps.chrome_dev",
"com.google.android.captiveportallogin", "com.google.android.captiveportallogin",
"com.iode.firefox",
"com.jamal2367.styx", "com.jamal2367.styx",
"com.kiwibrowser.browser", "com.kiwibrowser.browser",
"com.kiwibrowser.browser.dev", "com.kiwibrowser.browser.dev",
"com.lemurbrowser.exts",
"com.microsoft.emmx", "com.microsoft.emmx",
"com.microsoft.emmx.beta", "com.microsoft.emmx.beta",
"com.microsoft.emmx.canary", "com.microsoft.emmx.canary",
@@ -82,13 +88,16 @@ namespace Bit.Droid.Autofill
"com.mmbox.xbrowser", "com.mmbox.xbrowser",
"com.mycompany.app.soulbrowser", "com.mycompany.app.soulbrowser",
"com.naver.whale", "com.naver.whale",
"com.neeva.app",
"com.opera.browser", "com.opera.browser",
"com.opera.browser.beta", "com.opera.browser.beta",
"com.opera.gx",
"com.opera.mini.native", "com.opera.mini.native",
"com.opera.mini.native.beta", "com.opera.mini.native.beta",
"com.opera.touch", "com.opera.touch",
"com.qflair.browserq", "com.qflair.browserq",
"com.qwant.liberty", "com.qwant.liberty",
"com.rainsee.create",
"com.sec.android.app.sbrowser", "com.sec.android.app.sbrowser",
"com.sec.android.app.sbrowser.beta", "com.sec.android.app.sbrowser.beta",
"com.stoutner.privacybrowser.free", "com.stoutner.privacybrowser.free",
@@ -97,6 +106,9 @@ namespace Bit.Droid.Autofill
"com.vivaldi.browser.snapshot", "com.vivaldi.browser.snapshot",
"com.vivaldi.browser.sopranos", "com.vivaldi.browser.sopranos",
"com.yandex.browser", "com.yandex.browser",
"com.yjllq.internet",
"com.yjllq.kito",
"com.yujian.ResideMenuDemo",
"com.z28j.feel", "com.z28j.feel",
"idm.internet.download.manager", "idm.internet.download.manager",
"idm.internet.download.manager.adm.lite", "idm.internet.download.manager.adm.lite",
@@ -104,6 +116,7 @@ namespace Bit.Droid.Autofill
"io.github.forkmaintainers.iceraven", "io.github.forkmaintainers.iceraven",
"mark.via", "mark.via",
"mark.via.gp", "mark.via.gp",
"net.dezor.browser",
"net.slions.fulguris.full.download", "net.slions.fulguris.full.download",
"net.slions.fulguris.full.download.debug", "net.slions.fulguris.full.download.debug",
"net.slions.fulguris.full.playstore", "net.slions.fulguris.full.playstore",
@@ -202,7 +215,7 @@ namespace Bit.Droid.Autofill
} }
} }
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i], var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
inlinePresentationSpec); true, inlinePresentationSpec);
if (dataset != null) if (dataset != null)
{ {
responseBuilder.AddDataset(dataset); responseBuilder.AddDataset(dataset);
@@ -216,7 +229,7 @@ namespace Bit.Droid.Autofill
} }
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem, public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
InlinePresentationSpec inlinePresentationSpec = null) bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null)
{ {
var overlayPresentation = BuildOverlayPresentation( var overlayPresentation = BuildOverlayPresentation(
filledItem.Name, filledItem.Name,
@@ -237,6 +250,15 @@ namespace Bit.Droid.Autofill
{ {
datasetBuilder.SetInlinePresentation(inlinePresentation); datasetBuilder.SetInlinePresentation(inlinePresentation);
} }
if (includeAuthIntent)
{
var intent = new Intent(context, typeof(AutofillExternalSelectionActivity));
intent.PutExtra(AutofillConstants.AutofillFramework, true);
intent.PutExtra(AutofillConstants.AutofillFrameworkCipherId, filledItem.Id);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
}
if (filledItem.ApplyToFields(fields, datasetBuilder)) if (filledItem.ApplyToFields(fields, datasetBuilder))
{ {
return datasetBuilder.Build(); return datasetBuilder.Build();
@@ -248,26 +270,26 @@ namespace Bit.Droid.Autofill
IList<InlinePresentationSpec> inlinePresentationSpecs = null) IList<InlinePresentationSpec> inlinePresentationSpecs = null)
{ {
var intent = new Intent(context, typeof(MainActivity)); var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra("autofillFramework", true); intent.PutExtra(AutofillConstants.AutofillFramework, true);
if (fields.FillableForLogin) if (fields.FillableForLogin)
{ {
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login); intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login);
} }
else if (fields.FillableForCard) else if (fields.FillableForCard)
{ {
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card); intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card);
} }
else if (fields.FillableForIdentity) else if (fields.FillableForIdentity)
{ {
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity); intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity);
} }
else else
{ {
return null; return null;
} }
intent.PutExtra("autofillFrameworkUri", uri); intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
PendingIntentFlags.CancelCurrent); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
var overlayPresentation = BuildOverlayPresentation( var overlayPresentation = BuildOverlayPresentation(
AppResources.AutofillWithBitwarden, AppResources.AutofillWithBitwarden,
@@ -320,7 +342,7 @@ namespace Bit.Droid.Autofill
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the // InlinePresentation requires nonNull pending intent (even though we only utilize one for the
// "my vault" presentation) so we're including an empty one here // "my vault" presentation) so we're including an empty one here
pendingIntent = PendingIntent.GetService(context, 0, new Intent(), pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
} }
var slice = CreateInlinePresentationSlice( var slice = CreateInlinePresentationSlice(
inlinePresentationSpec, inlinePresentationSpec,

View File

@@ -1,4 +1,7 @@
using Android; using System;
using System.Collections.Generic;
using System.Linq;
using Android;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
@@ -9,16 +12,10 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bit.Droid.Autofill namespace Bit.Droid.Autofill
{ {
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")] [Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })] [IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
[MetaData("android.autofill", Resource = "@xml/autofillservice")] [MetaData("android.autofill", Resource = "@xml/autofillservice")]
[Register("com.x8bit.bitwarden.Autofill.AutofillService")] [Register("com.x8bit.bitwarden.Autofill.AutofillService")]
@@ -26,9 +23,9 @@ namespace Bit.Droid.Autofill
{ {
private ICipherService _cipherService; private ICipherService _cipherService;
private IVaultTimeoutService _vaultTimeoutService; private IVaultTimeoutService _vaultTimeoutService;
private IStorageService _storageService;
private IPolicyService _policyService; private IPolicyService _policyService;
private IUserService _userService; private IStateService _stateService;
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback) FillCallback callback)
@@ -44,18 +41,18 @@ namespace Bit.Droid.Autofill
var parser = new Parser(structure, ApplicationContext); var parser = new Parser(structure, ApplicationContext);
parser.Parse(); parser.Parse();
if (_storageService == null) if (_stateService == null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
} }
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService); var shouldAutofill = await parser.ShouldAutofillAsync(_stateService);
if (!shouldAutofill) if (!shouldAutofill)
{ {
return; return;
} }
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true; var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
if (_vaultTimeoutService == null) if (_vaultTimeoutService == null)
{ {
@@ -76,7 +73,7 @@ namespace Bit.Droid.Autofill
// build response // build response
var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request); var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request);
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey); var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (!disableSavePrompt.GetValueOrDefault()) if (!disableSavePrompt.GetValueOrDefault())
{ {
AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection); AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection);
@@ -85,9 +82,7 @@ namespace Bit.Droid.Autofill
} }
catch (Exception e) catch (Exception e)
{ {
#if !FDROID _logger.Value.Exception(e);
Crashes.TrackError(e);
#endif
} }
} }
@@ -101,12 +96,12 @@ namespace Bit.Droid.Autofill
return; return;
} }
if (_storageService == null) if (_stateService == null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
} }
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey); var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (disableSavePrompt.GetValueOrDefault()) if (disableSavePrompt.GetValueOrDefault())
{ {
return; return;
@@ -139,7 +134,7 @@ namespace Bit.Droid.Autofill
{ {
case CipherType.Login: case CipherType.Login:
intent.PutExtra("autofillFrameworkName", parser.Uri intent.PutExtra("autofillFrameworkName", parser.Uri
.Replace(Constants.AndroidAppProtocol, string.Empty) .Replace(Core.Constants.AndroidAppProtocol, string.Empty)
.Replace("https://", string.Empty) .Replace("https://", string.Empty)
.Replace("http://", string.Empty)); .Replace("http://", string.Empty));
intent.PutExtra("autofillFrameworkUri", parser.Uri); intent.PutExtra("autofillFrameworkUri", parser.Uri);
@@ -161,9 +156,7 @@ namespace Bit.Droid.Autofill
} }
catch (Exception e) catch (Exception e)
{ {
#if !FDROID _logger.Value.Exception(e);
Crashes.TrackError(e);
#endif
} }
} }
} }

View File

@@ -12,6 +12,7 @@ namespace Bit.Droid.Autofill
private List<Field> _passwordFields = null; private List<Field> _passwordFields = null;
private List<Field> _usernameFields = null; private List<Field> _usernameFields = null;
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" }; private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username"};
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" }; private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>(); public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
@@ -98,6 +99,11 @@ namespace Bit.Droid.Autofill
_usernameFields.Add(usernameField); _usernameFields.Add(usernameField);
} }
} }
if (!_usernameFields.Any())
{
_usernameFields = Fields.Where(f => FieldHasUsernameTerms(f)).ToList();
}
} }
return _usernameFields; return _usernameFields;
} }
@@ -321,7 +327,7 @@ namespace Bit.Droid.Autofill
} }
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) && return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms); !ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms) && !FieldHasUsernameTerms(f);
} }
private bool FieldHasPasswordTerms(Field f) private bool FieldHasPasswordTerms(Field f)
@@ -329,6 +335,11 @@ namespace Bit.Droid.Autofill
return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms); return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms);
} }
private bool FieldHasUsernameTerms(Field f)
{
return ValueContainsAnyTerms(f.IdEntry, _usernameTerms) || ValueContainsAnyTerms(f.Hint, _usernameTerms);
}
private bool ValueContainsAnyTerms(string value, HashSet<string> terms) private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))

View File

@@ -23,6 +23,7 @@ namespace Bit.Droid.Autofill
public FilledItem(CipherView cipher) public FilledItem(CipherView cipher)
{ {
Id = cipher.Id;
Name = cipher.Name; Name = cipher.Name;
Type = cipher.Type; Type = cipher.Type;
Subtitle = cipher.SubTitle; Subtitle = cipher.SubTitle;
@@ -55,6 +56,7 @@ namespace Bit.Droid.Autofill
} }
} }
public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Subtitle { get; set; } = string.Empty; public string Subtitle { get; set; } = string.Empty;
public int Icon { get; set; } = Resource.Drawable.login; public int Icon { get; set; } = Resource.Drawable.login;

View File

@@ -48,7 +48,7 @@ namespace Bit.Droid.Autofill
} }
else else
{ {
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName); _uri = string.Concat(Core.Constants.AndroidAppProtocol, PackageName);
} }
return _uri; return _uri;
} }
@@ -80,13 +80,13 @@ namespace Bit.Droid.Autofill
} }
} }
public async Task<bool> ShouldAutofillAsync(IStorageService storageService) public async Task<bool> ShouldAutofillAsync(IStateService stateService)
{ {
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) && var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
FieldCollection != null && FieldCollection.Fillable; FieldCollection != null && FieldCollection.Fillable;
if (fillable) if (fillable)
{ {
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey); var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync();
if (blacklistedUris != null && blacklistedUris.Count > 0) if (blacklistedUris != null && blacklistedUris.Count > 0)
{ {
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri); fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);

7
src/Android/Constants.cs Normal file
View File

@@ -0,0 +1,7 @@
namespace Bit.Droid
{
public static class Constants
{
public const string PACKAGE_NAME = "com.x8bit.bitwarden";
}
}

View File

@@ -0,0 +1,24 @@
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(NoEmojiKeyboardEffect), nameof(NoEmojiKeyboardEffect))]
namespace Bit.Droid.Effects
{
public class NoEmojiKeyboardEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is EditText editText)
{
editText.InputType = Android.Text.InputTypes.ClassText | Android.Text.InputTypes.TextVariationVisiblePassword | Android.Text.InputTypes.TextFlagMultiLine;
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -0,0 +1,23 @@
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(RemoveFontPaddingEffect), nameof(RemoveFontPaddingEffect))]
namespace Bit.Droid.Effects
{
public class RemoveFontPaddingEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is TextView textView)
{
textView.SetIncludeFontPadding(false);
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -5,20 +5,27 @@ using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Content.Res;
using Android.Nfc; using Android.Nfc;
using Android.OS; using Android.OS;
using Android.Runtime; using Android.Runtime;
using AndroidX.Core.Content; using Android.Views;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xamarin.Essentials;
using ZXing.Net.Mobile.Android; using ZXing.Net.Mobile.Android;
using FileProvider = AndroidX.Core.Content.FileProvider;
namespace Bit.Droid namespace Bit.Droid
{ {
@@ -30,11 +37,14 @@ namespace Bit.Droid
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{ {
private IDeviceActionService _deviceActionService; private IDeviceActionService _deviceActionService;
private IFileService _fileService;
private IMessagingService _messagingService; private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
private IUserService _userService; private IStateService _stateService;
private IAppIdService _appIdService; private IAppIdService _appIdService;
private IEventService _eventService; private IEventService _eventService;
private IPushNotificationListenerService _pushNotificationListenerService;
private ILogger _logger;
private PendingIntent _eventUploadPendingIntent; private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions; private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}"; private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
@@ -45,17 +55,20 @@ namespace Bit.Droid
{ {
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver)); var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent, _eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false));
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build(); var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy); StrictMode.SetThreadPolicy(policy);
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_fileService = ServiceContainer.Resolve<IFileService>();
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_userService = ServiceContainer.Resolve<IUserService>("userService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService"); _appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
_logger = ServiceContainer.Resolve<ILogger>("logger");
TabLayoutResource = Resource.Layout.Tabbar; TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar; ToolbarResource = Resource.Layout.Toolbar;
@@ -64,20 +77,26 @@ namespace Bit.Droid
Intent?.Validate(); Intent?.Validate();
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
if (!CoreHelpers.InDebugMode())
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
{ {
Window.AddFlags(Android.Views.WindowManagerFlags.Secure); Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
} });
#if !FDROID _logger.InitAsync();
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
var appCenterTask = appCenterHelper.InitAsync(); var toplayout = Window?.DecorView?.RootView;
#endif if (toplayout != null)
{
toplayout.FilterTouchesWhenObscured = true;
}
Xamarin.Essentials.Platform.Init(this, savedInstanceState); Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState); Xamarin.Forms.Forms.Init(this, savedInstanceState);
_appOptions = GetOptions(); _appOptions = GetOptions();
CreateNotificationChannel();
LoadApplication(new App.App(_appOptions)); LoadApplication(new App.App(_appOptions));
DisableAndroidFontScale();
_broadcasterService.Subscribe(_activityKey, (message) => _broadcasterService.Subscribe(_activityKey, (message) =>
{ {
@@ -133,6 +152,15 @@ namespace Bit.Droid
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this) AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
.GetAwaiter() .GetAwaiter()
.GetResult(); .GetResult();
if (Intent?.GetStringExtra(Core.Constants.NotificationData) is string notificationDataJson)
{
var notificationType = JToken.Parse(notificationDataJson).SelectToken(Core.Constants.NotificationDataType);
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.OnNotificationTapped(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
}
}
} }
protected override void OnNewIntent(Intent intent) protected override void OnNewIntent(Intent intent)
@@ -186,13 +214,13 @@ namespace Bit.Droid
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
[GeneratedEnum] Permission[] grantResults) [GeneratedEnum] Permission[] grantResults)
{ {
if (requestCode == Constants.SelectFilePermissionRequestCode) if (requestCode == Core.Constants.SelectFilePermissionRequestCode)
{ {
if (grantResults.Any(r => r != Permission.Granted)) if (grantResults.Any(r => r != Permission.Granted))
{ {
_messagingService.Send("selectFileCameraPermissionDenied"); _messagingService.Send("selectFileCameraPermissionDenied");
} }
await _deviceActionService.SelectFileAsync(); await _fileService.SelectFileAsync();
} }
else else
{ {
@@ -205,7 +233,7 @@ namespace Bit.Droid
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{ {
if (resultCode == Result.Ok && if (resultCode == Result.Ok &&
(requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode)) (requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
{ {
Android.Net.Uri uri = null; Android.Net.Uri uri = null;
string fileName = null; string fileName = null;
@@ -227,7 +255,7 @@ namespace Bit.Droid
return; return;
} }
if (requestCode == Constants.SaveFileRequestCode) if (requestCode == Core.Constants.SaveFileRequestCode)
{ {
_messagingService.Send("selectSaveFileResult", _messagingService.Send("selectSaveFileResult",
new Tuple<string, string>(uri.ToString(), fileName)); new Tuple<string, string>(uri.ToString(), fileName));
@@ -268,7 +296,7 @@ namespace Bit.Droid
{ {
var intent = new Intent(this, Class); var intent = new Intent(this, Class);
intent.AddFlags(ActivityFlags.SingleTop); intent.AddFlags(ActivityFlags.SingleTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0); var pendingIntent = PendingIntent.GetActivity(this, 0, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(0, true));
// register for all NDEF tags starting with http och https // register for all NDEF tags starting with http och https
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered); var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
ndef.AddDataScheme("http"); ndef.AddDataScheme("http");
@@ -295,13 +323,13 @@ namespace Bit.Droid
{ {
var options = new AppOptions var options = new AppOptions
{ {
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"), Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false), MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false), GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false), FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
CreateSend = GetCreateSendRequest(Intent) CreateSend = GetCreateSendRequest(Intent)
}; };
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0); var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);
if (fillType > 0) if (fillType > 0)
{ {
options.FillType = (CipherType)fillType; options.FillType = (CipherType)fillType;
@@ -375,7 +403,7 @@ namespace Bit.Droid
{ {
Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor); Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor);
Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor); Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor);
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled()); ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled());
} }
private void ExitApp() private void ExitApp()
@@ -396,5 +424,38 @@ namespace Bit.Droid
alarmManager.Cancel(_eventUploadPendingIntent); alarmManager.Cancel(_eventUploadPendingIntent);
await _eventService.UploadEventsAsync(); await _eventService.UploadEventsAsync();
} }
private void CreateNotificationChannel()
{
#if !FDROID
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channel = new NotificationChannel(Core.Constants.AndroidNotificationChannelId, AppResources.AllNotifications, NotificationImportance.Default);
if(GetSystemService(NotificationService) is NotificationManager notificationManager)
{
notificationManager.CreateNotificationChannel(channel);
}
#endif
}
private void DisableAndroidFontScale()
{
try
{
//As we are using NamedSizes the xamarin will change the font size. So we are disabling the Android scaling.
Resources.Configuration.FontScale = 1f;
BaseContext.Resources.DisplayMetrics.ScaledDensity = Resources.Configuration.FontScale * (float)DeviceDisplay.MainDisplayInfo.Density;
}
catch (Exception e)
{
_logger.Exception(e);
}
}
} }
} }

View File

@@ -12,7 +12,6 @@ using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Services; using Bit.Droid.Services;
using Bit.Droid.Utilities;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using Plugin.Fingerprint; using Plugin.Fingerprint;
using Xamarin.Android.Net; using Xamarin.Android.Net;
@@ -20,6 +19,8 @@ using System.Net.Http;
using System.Net; using System.Net;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls;
#if !FDROID #if !FDROID
using Android.Gms.Security; using Android.Gms.Security;
#endif #endif
@@ -44,23 +45,44 @@ namespace Bit.Droid
if (ServiceContainer.RegisteredServices.Count == 0) if (ServiceContainer.RegisteredServices.Count == 0)
{ {
RegisterLocalServices(); RegisterLocalServices();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey, ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey,
Constants.AndroidAllClearCipherCacheKeys); Core.Constants.AndroidAllClearCipherCacheKeys);
ServiceContainer.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IEnvironmentService>(),
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>()));
InitializeAppSetup();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged // TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner( var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
ServiceContainer.Resolve<IApiService>("apiService"), ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"), ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"), ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService")); ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<ILogger>("logger"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner); ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper( var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"), ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService")); ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper); ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"),
ServiceContainer.Resolve<ILogger>("logger"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<IWatchDeviceService>());
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -87,7 +109,15 @@ namespace Bit.Droid
private void RegisterLocalServices() private void RegisterLocalServices()
{ {
ServiceContainer.Register<ILogService>("logService", new AndroidLogService()); ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
#if FDROID
var logger = new StubLogger();
#elif DEBUG
var logger = DebugLogger.Instance;
#else
var logger = Logger.Instance;
#endif
ServiceContainer.Register("logger", logger);
// Note: This might cause a race condition. Investigate more. // Note: This might cause a race condition. Investigate more.
Task.Run(() => Task.Run(() =>
@@ -107,19 +137,25 @@ namespace Bit.Droid
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db")); var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
var localizeService = new LocalizeService(); var localizeService = new LocalizeService();
var broadcasterService = new BroadcasterService(); var broadcasterService = new BroadcasterService(logger);
var messagingService = new MobileBroadcasterMessagingService(broadcasterService); var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo()); var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
var secureStorageService = new SecureStorageService(); var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService(); var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService, var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService")); var stateMigrationService =
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
broadcasterService); var clipboardService = new ClipboardService(stateService);
var deviceActionService = new DeviceActionService(stateService, messagingService);
var fileService = new FileService(stateService, broadcasterService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService(); var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService); ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
@@ -129,13 +165,18 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService); ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService); ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(mobileStorageService)); ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService); ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IFileService>(fileService);
ServiceContainer.Register<IAutofillHandler>(autofillHandler);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService); ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService); ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService); ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService); ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService); ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
// Push // Push
#if FDROID #if FDROID
@@ -148,7 +189,7 @@ namespace Bit.Droid
ServiceContainer.Register<IPushNotificationListenerService>( ServiceContainer.Register<IPushNotificationListenerService>(
"pushNotificationListenerService", notificationListenerService); "pushNotificationListenerService", notificationListenerService);
var androidPushNotificationService = new AndroidPushNotificationService( var androidPushNotificationService = new AndroidPushNotificationService(
mobileStorageService, notificationListenerService); stateService, notificationListenerService);
ServiceContainer.Register<IPushNotificationService>( ServiceContainer.Register<IPushNotificationService>(
"pushNotificationService", androidPushNotificationService); "pushNotificationService", androidPushNotificationService);
#endif #endif
@@ -164,11 +205,14 @@ namespace Bit.Droid
private async Task BootstrapAsync() private async Task BootstrapAsync()
{ {
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService")
.GetAsync<bool?>(Constants.DisableFaviconKey);
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync(); await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
} }
private void InitializeAppSetup()
{
var appSetup = new AppSetup();
appSetup.InitializeServicesLastChance();
ServiceContainer.Register<IAppSetup>("appSetup", appSetup);
}
} }
} }

View File

@@ -1,57 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.15.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.2.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC"/> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/> <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false"/> <application android:label="Bitwarden" android:theme="@style/LaunchTheme" android:allowBackup="false" tools:replace="android:allowBackup" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
<application android:label="Bitwarden" android:theme="@style/LaunchTheme" android:allowBackup="false" tools:replace="android:allowBackup" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config"> </provider>
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/> <meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
</provider> <!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true" />
<meta-data android:name="android.max_aspect" android:value="2.1"/> <meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true" />
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/> <!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true" />
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) --> <!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true"/> <activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true"/> <intent-filter>
<action android:name="android.intent.action.MAIN" />
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) --> <category android:name="android.intent.category.LAUNCHER" />
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true"/> </intent-filter>
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource --> <intent-filter>
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme"> <action android:name="android.intent.action.SEND" />
<intent-filter> <category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN"/> <data android:mimeType="application/*" />
<category android:name="android.intent.category.LAUNCHER"/> <data android:mimeType="image/*" />
</intent-filter> <data android:mimeType="video/*" />
<intent-filter> <data android:mimeType="text/*" />
<action android:name="android.intent.action.SEND"/> </intent-filter>
<category android:name="android.intent.category.DEFAULT"/> </activity>
<data android:mimeType="application/*"/> </application>
<data android:mimeType="image/*"/> <!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
<data android:mimeType="video/*"/> <!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
<data android:mimeType="text/*"/> <queries>
</intent-filter> <intent>
</activity> <action android:name="android.intent.action.VIEW" />
</application> <data android:scheme="http"/>
</intent>
<!-- Package visibility (for Android 11+) --> <intent>
<queries> <action android:name="android.intent.action.VIEW" />
<intent> <data android:scheme="https"/>
<action android:name="*"/> </intent>
</intent> </queries>
</queries> </manifest>
</manifest>

View File

@@ -1,7 +1,9 @@
#if !FDROID #if !FDROID
using System;
using Android.App; using Android.App;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Firebase.Messaging; using Firebase.Messaging;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -16,34 +18,41 @@ namespace Bit.Droid.Push
{ {
public async override void OnNewToken(string token) public async override void OnNewToken(string token)
{ {
var storageService = ServiceContainer.Resolve<IStorageService>("storageService"); try {
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService"); var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token); await stateService.SetPushRegisteredTokenAsync(token);
await pushNotificationService.RegisterAsync(); await pushNotificationService.RegisterAsync();
}
catch (Exception ex)
{
Logger.Instance.Exception(ex);
}
} }
public async override void OnMessageReceived(RemoteMessage message) public async override void OnMessageReceived(RemoteMessage message)
{ {
if (message?.Data == null)
{
return;
}
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
if (data == null)
{
return;
}
try try
{ {
if (message?.Data == null)
{
return;
}
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
if (data == null)
{
return;
}
var obj = JObject.Parse(data); var obj = JObject.Parse(data);
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>( var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
"pushNotificationListenerService"); "pushNotificationListenerService");
await listener.OnMessageAsync(obj, Device.Android); await listener.OnMessageAsync(obj, Device.Android);
} }
catch (JsonReaderException ex) catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine(ex.ToString()); Logger.Instance.Exception(ex);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using Android.Content; using Android.Content;
using Android.OS;
namespace Bit.Droid.Receivers namespace Bit.Droid.Receivers
{ {
@@ -8,7 +9,17 @@ namespace Bit.Droid.Receivers
public override void OnReceive(Context context, Intent intent) public override void OnReceive(Context context, Intent intent)
{ {
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager; var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " "); if (clipboardManager == null)
{
return;
}
// ClearPrimaryClip is supported down to API 28 with mixed results, so we're requiring 33+ instead
if ((int)Build.VERSION.SdkInt < 33)
{
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
return;
}
clipboardManager.ClearPrimaryClip();
} }
} }
} }

View File

@@ -0,0 +1,41 @@
using Android.Content;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Services;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using CoreConstants = Bit.Core.Constants;
namespace Bit.Droid.Receivers
{
[BroadcastReceiver(Name = Constants.PACKAGE_NAME + "." + nameof(NotificationDismissReceiver), Exported = false)]
public class NotificationDismissReceiver : BroadcastReceiver
{
private readonly LazyResolve<IPushNotificationListenerService> _pushNotificationListenerService = new LazyResolve<IPushNotificationListenerService>();
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public override void OnReceive(Context context, Intent intent)
{
try
{
if (intent?.GetStringExtra(CoreConstants.NotificationData) is string notificationDataJson)
{
var notificationType = JToken.Parse(notificationDataJson).SelectToken(CoreConstants.NotificationDataType);
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.Value.OnNotificationDismissed(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
}
}
}
catch (System.Exception ex)
{
_logger.Value.Exception(ex);
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System; using Android.App;
using Android.App;
using Android.Content; using Android.Content;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Utilities; using Bit.App.Utilities;
@@ -14,9 +13,10 @@ namespace Bit.Droid.Receivers
{ {
public override async void OnReceive(Context context, Intent intent) public override async void OnReceive(Context context, Intent intent)
{ {
var storageService = ServiceContainer.Resolve<IStorageService>("storageService"); await AppHelpers.PerformUpdateTasksAsync(
await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve<ISyncService>("syncService"), ServiceContainer.Resolve<ISyncService>("syncService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"), storageService); ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<IStateService>("stateService"));
} }
} }
} }

View File

@@ -0,0 +1,31 @@
using System;
using Android.App;
using Android.Content;
using AndroidX.AppCompat.Widget;
using Bit.App.Resources;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomPageRenderer : PageRenderer
{
public CustomPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
Activity context = (Activity)this.Context;
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
if(toolbar != null)
{
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
}
}
}
}

View File

@@ -44,6 +44,7 @@ namespace Bit.Droid.Renderers
} }
if (Control != null) if (Control != null)
{ {
Control.SetHintTextColor(ThemeHelpers.MutedColor);
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null); var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb) if (t is GradientDrawable thumb)
{ {

View File

@@ -1,7 +1,9 @@
using Android.Content; using Android.Content;
using Android.Views; using Android.Views;
using Bit.App.Pages;
using Bit.Droid.Renderers; using Bit.Droid.Renderers;
using Google.Android.Material.BottomNavigation; using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Navigation;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.Android; using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat; using Xamarin.Forms.Platform.Android.AppCompat;
@@ -9,7 +11,7 @@ using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))] [assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.Droid.Renderers namespace Bit.Droid.Renderers
{ {
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener
{ {
private TabbedPage _page; private TabbedPage _page;
@@ -21,7 +23,7 @@ namespace Bit.Droid.Renderers
if (e.NewElement != null) if (e.NewElement != null)
{ {
_page = e.NewElement; _page = e.NewElement;
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this); GetBottomNavigationView()?.SetOnItemReselectedListener(this);
} }
else else
{ {
@@ -53,6 +55,10 @@ namespace Bit.Droid.Renderers
{ {
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0) if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{ {
if (_page is TabsPage tabsPage)
{
tabsPage.OnPageReselected();
}
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync()); Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
} }
} }

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/darkgray"/>
<foreground android:drawable="@drawable/logo_rounded"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/logo_rounded"/>
</adaptive-icon>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="640dp"
android:height="512dp"
android:viewportWidth="640"
android:viewportHeight="512">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
</vector>

View File

@@ -6,4 +6,4 @@
<path <path
android:fillColor="#FF000000" android:fillColor="#FF000000"
android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" /> android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
</vector> </vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.099"
android:scaleY="0.099"
android:translateX="24.3"
android:translateY="24.3">
<path
android:fillColor="#ffffff"
android:pathData="M481.4,102.2c-3.7,-3.7 -8.1,-5.6 -13.1,-5.6L131.7,96.6c-5.1,0 -9.4,1.9 -13.1,5.6C114.9,105.9 113,110.2 113,115.3v224.4c0,16.7 3.3,33.4 9.8,49.8c6.5,16.5 14.6,31.1 24.3,43.8c9.6,12.8 21.1,25.2 34.5,37.2c13.3,12.1 25.7,22.1 37,30.1c11.3,8 23.1,15.5 35.4,22.6c12.3,7.1 21,11.9 26.2,14.5c5.2,2.5 9.3,4.5 12.4,5.8c2.3,1.2 4.9,1.8 7.6,1.8c2.7,0 5.3,-0.6 7.6,-1.8c3.1,-1.4 7.3,-3.3 12.4,-5.8c5.2,-2.5 13.9,-7.4 26.2,-14.5c12.3,-7.1 24.1,-14.7 35.4,-22.6c11.3,-8 23.6,-18 37,-30.1c13.3,-12.1 24.8,-24.5 34.5,-37.2c9.6,-12.8 17.7,-27.4 24.2,-43.8c6.5,-16.5 9.8,-33.1 9.8,-49.8L487.3,115.3C487,110.2 485.1,105.9 481.4,102.2zM438,341.8C438,423 300,493 300,493L300,144.6h138C438,144.6 438,260.6 438,341.8z" />
</group>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="420"
android:viewportWidth="420" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M350.43,40.516C347.563,37.65 344.153,36.178 340.281,36.178L79.487,36.178C75.538,36.178 72.206,37.65 69.338,40.516C66.472,43.384 65,46.716 65,50.665L65,224.527C65,237.466 67.557,250.405 72.593,263.112C77.629,275.895 83.904,287.207 91.42,297.046C98.857,306.964 107.768,316.571 118.149,325.869C128.455,335.242 138.063,342.99 146.817,349.19C155.573,355.387 164.715,361.198 174.243,366.699C183.773,372.2 190.514,375.919 194.543,377.933C198.572,379.871 201.749,381.421 204.151,382.426C205.932,383.357 207.948,383.821 210.04,383.821C212.131,383.821 214.145,383.357 215.929,382.426C218.329,381.344 221.584,379.871 225.534,377.933C229.563,375.997 236.304,372.2 245.832,366.699C255.365,361.198 264.506,355.311 273.262,349.19C282.017,342.99 291.545,335.242 301.928,325.869C312.232,316.493 321.142,306.886 328.657,297.046C336.096,287.129 342.372,275.819 347.407,263.112C352.444,250.328 355,237.466 355,224.527L355,50.665C354.768,46.716 353.296,43.384 350.43,40.516ZM316.804,226.154C316.804,289.067 209.883,343.302 209.883,343.302L209.883,73.368L316.804,73.368C316.804,73.368 316.804,163.242 316.804,226.154Z"/>
</vector>

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.11454546"
android:scaleY="0.11454546"
android:translateX="31.663637"
android:translateY="27.54">
<path
android:pathData="M376.4,12.2c-3.7,-3.7 -8.1,-5.6 -13.1,-5.6H26.7c-5.1,0 -9.4,1.9 -13.1,5.6C9.9,15.9 8,20.2 8,25.3v224.4c0,16.7 3.3,33.4 9.8,49.8c6.5,16.5 14.6,31.1 24.3,43.8c9.6,12.8 21.1,25.2 34.5,37.2c13.3,12.1 25.7,22.1 37,30.1c11.3,8 23.1,15.5 35.4,22.6c12.3,7.1 21,11.9 26.2,14.5c5.2,2.5 9.3,4.5 12.4,5.8c2.3,1.2 4.9,1.8 7.6,1.8c2.7,0 5.3,-0.6 7.6,-1.8c3.1,-1.4 7.3,-3.3 12.4,-5.8c5.2,-2.5 13.9,-7.4 26.2,-14.5c12.3,-7.1 24.1,-14.7 35.4,-22.6c11.3,-8 23.6,-18 37,-30.1c13.3,-12.1 24.8,-24.5 34.5,-37.2c9.6,-12.8 17.7,-27.4 24.2,-43.8c6.5,-16.5 9.8,-33.1 9.8,-49.8V25.3C382,20.2 380.1,15.9 376.4,12.2zM333,251.8C333,333 195,403 195,403V54.6h138C333,54.6 333,170.6 333,251.8z"
android:fillColor="#FFFFFF"/>
</group>
</vector>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -4,6 +4,7 @@
<style name="LaunchTheme" parent="BaseTheme"> <style name="LaunchTheme" parent="BaseTheme">
<item name="android:windowBackground">@drawable/splash_screen_dark</item> <item name="android:windowBackground">@drawable/splash_screen_dark</item>
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
</style> </style>
<style name="BaseTheme" parent="Theme.AppCompat"> <style name="BaseTheme" parent="Theme.AppCompat">

View File

@@ -4,6 +4,8 @@
<style name="LaunchTheme" parent="BaseTheme"> <style name="LaunchTheme" parent="BaseTheme">
<item name="android:windowBackground">@drawable/splash_screen</item> <item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
</style> </style>
<style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">

View File

@@ -6,4 +6,5 @@
android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows" android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
android:notificationTimeout="100" android:notificationTimeout="100"
android:canRetrieveWindowContent="true"/> android:canRetrieveWindowContent="true"
android:isAccessibilityTool="false"/>

View File

@@ -11,6 +11,15 @@
--> -->
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android" <autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsInlineSuggestions="true"> android:supportsInlineSuggestions="true">
<compatibility-package
android:name="alook.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="alook.browser.google"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="app.vanadium.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.amazon.cloud9" android:name="com.amazon.cloud9"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -71,6 +80,9 @@
<compatibility-package <compatibility-package
android:name="com.google.android.captiveportallogin" android:name="com.google.android.captiveportallogin"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.iode.firefox"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.jamal2367.styx" android:name="com.jamal2367.styx"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -80,6 +92,9 @@
<compatibility-package <compatibility-package
android:name="com.kiwibrowser.browser.dev" android:name="com.kiwibrowser.browser.dev"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.lemurbrowser.exts"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.microsoft.emmx" android:name="com.microsoft.emmx"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -104,12 +119,18 @@
<compatibility-package <compatibility-package
android:name="com.naver.whale" android:name="com.naver.whale"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.neeva.app"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.opera.browser" android:name="com.opera.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.opera.browser.beta" android:name="com.opera.browser.beta"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.opera.gx"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.opera.mini.native" android:name="com.opera.mini.native"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -125,6 +146,9 @@
<compatibility-package <compatibility-package
android:name="com.qwant.liberty" android:name="com.qwant.liberty"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.rainsee.create"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.sec.android.app.sbrowser" android:name="com.sec.android.app.sbrowser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -149,6 +173,15 @@
<compatibility-package <compatibility-package
android:name="com.yandex.browser" android:name="com.yandex.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yjllq.internet"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yjllq.kito"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yujian.ResideMenuDemo"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.z28j.feel" android:name="com.z28j.feel"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -170,6 +203,9 @@
<compatibility-package <compatibility-package
android:name="mark.via.gp" android:name="mark.via.gp"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.dezor.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="net.slions.fulguris.full.download" android:name="net.slions.fulguris.full.download"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>

View File

@@ -3,7 +3,7 @@ using System;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public class AndroidLogService : ILogService public class AndroidLogService : INativeLogService
{ {
private static readonly string _tag = "BITWARDEN"; private static readonly string _tag = "BITWARDEN";

View File

@@ -1,37 +1,52 @@
#if !FDROID #if !FDROID
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using AndroidX.Core.App; using AndroidX.Core.App;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Bit.Droid.Utilities;
using Newtonsoft.Json;
using Xamarin.Forms; using Xamarin.Forms;
using static Xamarin.Essentials.Platform;
using Intent = Android.Content.Intent;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
public class AndroidPushNotificationService : IPushNotificationService public class AndroidPushNotificationService : IPushNotificationService
{ {
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly IPushNotificationListenerService _pushNotificationListenerService; private readonly IPushNotificationListenerService _pushNotificationListenerService;
public AndroidPushNotificationService( public AndroidPushNotificationService(
IStorageService storageService, IStateService stateService,
IPushNotificationListenerService pushNotificationListenerService) IPushNotificationListenerService pushNotificationListenerService)
{ {
_storageService = storageService; _stateService = stateService;
_pushNotificationListenerService = pushNotificationListenerService; _pushNotificationListenerService = pushNotificationListenerService;
} }
public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false; public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false;
public Task<bool> AreNotificationsSettingsEnabledAsync()
{
return Task.FromResult(IsRegisteredForPush);
}
public async Task<string> GetTokenAsync() public async Task<string> GetTokenAsync()
{ {
return await _storageService.GetAsync<string>(Constants.PushCurrentTokenKey); return await _stateService.GetPushCurrentTokenAsync();
} }
public async Task RegisterAsync() public async Task RegisterAsync()
{ {
var registeredToken = await _storageService.GetAsync<string>(Constants.PushRegisteredTokenKey); var registeredToken = await _stateService.GetPushRegisteredTokenAsync();
var currentToken = await GetTokenAsync(); var currentToken = await GetTokenAsync();
if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken) if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken)
{ {
@@ -39,7 +54,7 @@ namespace Bit.Droid.Services
} }
else else
{ {
await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow); await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow);
} }
} }
@@ -48,6 +63,50 @@ namespace Bit.Droid.Services
// Do we ever need to unregister? // Do we ever need to unregister?
return Task.FromResult(0); return Task.FromResult(0);
} }
public void DismissLocalNotification(string notificationId)
{
if (int.TryParse(notificationId, out int intNotificationId))
{
var notificationManager = NotificationManagerCompat.From(Android.App.Application.Context);
notificationManager.Cancel(intNotificationId);
}
}
public void SendLocalNotification(string title, string message, BaseNotificationData data)
{
if (string.IsNullOrEmpty(data.Id))
{
throw new ArgumentNullException("notificationId cannot be null or empty.");
}
var context = Android.App.Application.Context;
var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
.SetContentIntent(pendingIntent)
.SetContentTitle(title)
.SetContentText(message)
.SetSmallIcon(Resource.Drawable.ic_notification)
.SetColor((int)Android.Graphics.Color.White)
.SetDeleteIntent(deletePendingIntent)
.SetAutoCancel(true);
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
{
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
}
var notificationManager = NotificationManagerCompat.From(context);
notificationManager.Notify(int.Parse(data.Id), builder.Build());
}
} }
} }
#endif #endif

View File

@@ -0,0 +1,215 @@
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Views.Autofill;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
{
public class AutofillHandler : IAutofillHandler
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IClipboardService _clipboardService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly LazyResolve<IEventService> _eventService;
public AutofillHandler(IStateService stateService,
IMessagingService messagingService,
IClipboardService clipboardService,
IPlatformUtilsService platformUtilsService,
LazyResolve<IEventService> eventService)
{
_stateService = stateService;
_messagingService = messagingService;
_clipboardService = clipboardService;
_platformUtilsService = platformUtilsService;
_eventService = eventService;
}
public bool AutofillServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var afm = (AutofillManager)activity.GetSystemService(
Java.Lang.Class.FromType(typeof(AutofillManager)));
return afm.IsEnabled && afm.HasEnabledAutofillServices;
}
catch
{
return false;
}
}
public bool SupportsAutofillService()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
return manager.IsAutofillSupported;
}
catch
{
return false;
}
}
public void Autofill(CipherView cipher)
{
var activity = CrossCurrentActivity.Current.Activity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity;
if (activity == null)
{
return;
}
if (activity.Intent?.GetBooleanExtra(AutofillConstants.AutofillFramework, false) ?? false)
{
if (cipher == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var structure = activity.Intent.GetParcelableExtra(
AutofillManager.ExtraAssistStructure) as AssistStructure;
if (structure == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var parser = new Parser(structure, activity.ApplicationContext);
parser.Parse();
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var task = CopyTotpAsync(cipher);
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher), false);
var replyIntent = new Intent();
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent);
activity.Finish();
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
else
{
var data = new Intent();
if (cipher?.Login == null)
{
data.PutExtra("canceled", "true");
}
else
{
var task = CopyTotpAsync(cipher);
data.PutExtra("uri", cipher.Login.Uri);
data.PutExtra("username", cipher.Login.Username);
data.PutExtra("password", cipher.Login.Password);
}
if (activity.Parent == null)
{
activity.SetResult(Result.Ok, data);
}
else
{
activity.Parent.SetResult(Result.Ok, data);
}
activity.Finish();
_messagingService.Send("finishMainActivity");
if (cipher != null)
{
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
}
}
public void CloseAutofill()
{
Autofill(null);
}
public bool AutofillAccessibilityServiceRunning()
{
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
}
public bool AutofillAccessibilityOverlayPermitted()
{
return Accessibility.AccessibilityHelpers.OverlayPermitted();
}
public void DisableAutofillService()
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
manager.DisableAutofillServices();
}
catch { }
}
public bool AutofillServicesEnabled()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{
// Android 5-6: Both accessibility & overlay are required or nothing happens
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
}
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
{
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
return AutofillAccessibilityServiceRunning();
}
// Android 8+: Either autofill or accessibility is required
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
}
private async Task CopyTotpAsync(CipherView cipher)
{
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null)
{
await _clipboardService.CopyTextAsync(totp);
_platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
}
}
}
}
}
}

View File

@@ -1,11 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.OS; using Android.OS;
using Android.Security.Keystore; using Android.Security.Keystore;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Java.Security; using Java.Security;
using Javax.Crypto; using Javax.Crypto;
@@ -43,23 +42,22 @@ namespace Bit.Droid.Services
public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null) public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
{ {
// bioIntegrityKey used in iOS only
if (Build.VERSION.SdkInt < BuildVersionCodes.M) if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{ {
return Task.FromResult(true); return Task.FromResult(true);
} }
_keystore.Load(null);
IKey key = _keystore.GetKey(KeyName, null);
Cipher cipher = Cipher.GetInstance(Transformation);
if (key == null || cipher == null)
{
return Task.FromResult(true);
}
try try
{ {
_keystore.Load(null);
var key = _keystore.GetKey(KeyName, null);
var cipher = Cipher.GetInstance(Transformation);
if (key == null || cipher == null)
{
return Task.FromResult(true);
}
cipher.Init(CipherMode.EncryptMode, key); cipher.Init(CipherMode.EncryptMode, key);
} }
catch (KeyPermanentlyInvalidatedException e) catch (KeyPermanentlyInvalidatedException e)
@@ -75,6 +73,7 @@ namespace Bit.Droid.Services
catch (InvalidKeyException e) catch (InvalidKeyException e)
{ {
// Fallback for old bitwarden users without a key // Fallback for old bitwarden users without a key
LoggerHelper.LogEvenIfCantBeResolved(e);
CreateKey(); CreateKey();
} }
@@ -95,10 +94,11 @@ namespace Bit.Droid.Services
keyGen.Init(keyGenSpec); keyGen.Init(keyGenSpec);
keyGen.GenerateKey(); keyGen.GenerateKey();
} }
catch catch (Exception e)
{ {
// Catch silently to allow biometrics to function on devices that are in a state where key generation // Catch silently to allow biometrics to function on devices that are in a state where key generation
// is not functioning // is not functioning
LoggerHelper.LogEvenIfCantBeResolved(e);
} }
} }
} }

View File

@@ -2,35 +2,69 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Bit.Core; using Android.OS;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Plugin.CurrentActivity; using Bit.Droid.Utilities;
using Xamarin.Essentials; using Xamarin.Essentials;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
public class ClipboardService : IClipboardService public class ClipboardService : IClipboardService
{ {
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent; private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStorageService storageService) public ClipboardService(IStateService stateService)
{ {
_storageService = storageService; _stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() => _clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity, PendingIntent.GetBroadcast(Application.Context,
0, 0,
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)), new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)),
PendingIntentFlags.UpdateCurrent)); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
} }
public async Task CopyTextAsync(string text, int expiresInMs = -1) public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
{ {
await Clipboard.SetTextAsync(text); try
{
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
if ((int)Build.VERSION.SdkInt < 33)
{
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await ClearClipboardAlarmAsync(expiresInMs); await ClearClipboardAlarmAsync(expiresInMs);
}
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{
// #1962 Just ignore, the content is copied either way but there is some app interfering in the process
// that the OS catches and just throws this exception.
}
}
public bool IsCopyNotificationHandledByPlatform()
{
// Android 13+ provides built-in notification when text is copied to the clipboard
return (int)Build.VERSION.SdkInt >= 33;
}
private void CopyToClipboard(string text, bool isSensitive = true)
{
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive)
{
clipData.Description.Extras ??= new PersistableBundle();
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
}
clipboardManager.PrimaryClip = clipData;
} }
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1) private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
@@ -39,7 +73,7 @@ namespace Bit.Droid.Services
if (clearMs < 0) if (clearMs < 0)
{ {
// if not set then we need to check if the user set this config // if not set then we need to check if the user set this config
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey); var clearSeconds = await _stateService.GetClearClipboardAsync();
if (clearSeconds != null) if (clearSeconds != null)
{ {
clearMs = clearSeconds.Value * 1000; clearMs = clearSeconds.Value * 1000;
@@ -50,7 +84,7 @@ namespace Bit.Droid.Services
return; return;
} }
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs; var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager; var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value); alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
} }
} }

View File

@@ -5,6 +5,8 @@ using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Parameters;
using System; using System;
using System.Runtime.InteropServices;
using Java.Lang;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
@@ -33,5 +35,19 @@ namespace Bit.Droid.Services
generator.Init(password, salt, iterations); generator.Init(password, salt, iterations);
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey(); return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
} }
public byte[] Argon2id(byte[] password, byte[] salt, int iterations, int memory, int parallelism)
{
JavaSystem.LoadLibrary("argon2");
int keySize = 32;
var key = new byte[keySize];
argon2id_hash_raw(iterations, memory, parallelism,
password, password.Length, salt, salt.Length, key, key.Length);
return key;
}
[DllImport("argon2", EntryPoint = "argon2id_hash_raw")]
private static extern int argon2id_hash_raw(int timeCost, int memoryCost, int parallelism,
byte[] pwd, int pwdlen, byte[] salt, int saltlen, byte[] hash, int hashlen);
} }
} }

View File

@@ -1,11 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android;
using Android.App; using Android.App;
using Android.App.Assist;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Nfc; using Android.Nfc;
@@ -14,56 +9,35 @@ using Android.Provider;
using Android.Text; using Android.Text;
using Android.Text.Method; using Android.Text.Method;
using Android.Views; using Android.Views;
using Android.Views.Autofill;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Webkit;
using Android.Widget; using Android.Widget;
using AndroidX.Core.App;
using AndroidX.Core.Content;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using static Bit.App.Pages.SettingsPageViewModel;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
public class DeviceActionService : IDeviceActionService public class DeviceActionService : IDeviceActionService
{ {
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func<IEventService> _eventServiceFunc;
private AlertDialog _progressDialog; private AlertDialog _progressDialog;
object _progressDialogLock = new object(); object _progressDialogLock = new object();
private bool _cameraPermissionsDenied;
private Toast _toast; private Toast _toast;
private string _userAgent; private string _userAgent;
public DeviceActionService( public DeviceActionService(
IStorageService storageService, IStateService stateService,
IMessagingService messagingService, IMessagingService messagingService)
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{ {
_storageService = storageService; _stateService = stateService;
_messagingService = messagingService; _messagingService = messagingService;
_broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc;
_broadcasterService.Subscribe(nameof(DeviceActionService), (message) =>
{
if (message.Command == "selectFileCameraPermissionDenied")
{
_cameraPermissionsDenied = true;
}
});
} }
public string DeviceUserAgent public string DeviceUserAgent
@@ -96,14 +70,17 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName) public bool LaunchApp(string appName)
{ {
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = CrossCurrentActivity.Current.Activity; var activity = CrossCurrentActivity.Current.Activity;
appName = appName.Replace("androidapp://", string.Empty); appName = appName.Replace("androidapp://", string.Empty);
var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName); var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
if (launchIntent != null) launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
{ return launchIntentSender != null;
activity.StartActivity(launchIntent);
}
return launchIntent != null;
} }
public async Task ShowLoadingAsync(string text) public async Task ShowLoadingAsync(string text)
@@ -209,184 +186,6 @@ namespace Bit.Droid.Services
return true; return true;
} }
public bool OpenFile(byte[] fileData, string id, string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(fileData, fileName);
if (intent == null)
{
return false;
}
activity.StartActivity(intent);
return true;
}
catch { }
return false;
}
public bool CanOpenFile(string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
if (intent == null)
{
return false;
}
var activities = activity.PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
catch { }
return false;
}
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return null;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
return null;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if (!file.IsFile)
{
return null;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
return intent;
}
catch { }
return null;
}
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (contentUri != null)
{
var uri = Android.Net.Uri.Parse(contentUri);
var stream = activity.ContentResolver.OpenOutputStream(uri);
// Using java bufferedOutputStream due to this issue:
// https://github.com/xamarin/xamarin-android/issues/3498
var javaStream = new Java.IO.BufferedOutputStream(stream);
javaStream.Write(fileData);
javaStream.Flush();
javaStream.Close();
return true;
}
// Prompt for location to save file
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return false;
}
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
// Unable to identify so fall back to generic "any" type
mimeType = "*/*";
}
var intent = new Intent(Intent.ActionCreateDocument);
intent.SetType(mimeType);
intent.AddCategory(Intent.CategoryOpenable);
intent.PutExtra(Intent.ExtraTitle, fileName);
activity.StartActivityForResult(intent, Constants.SaveFileRequestCode);
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
}
return false;
}
public async Task ClearCacheAsync()
{
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _storageService.SaveAsync(Constants.LastFileCacheClearKey, DateTime.UtcNow);
}
catch (Exception) { }
}
public Task SelectFileAsync()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var hasStorageWritePermission = !_cameraPermissionsDenied &&
HasPermission(Manifest.Permission.WriteExternalStorage);
var additionalIntents = new List<IParcelable>();
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
{
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
{
AskPermission(Manifest.Permission.WriteExternalStorage);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && !hasCameraPermission)
{
AskPermission(Manifest.Permission.Camera);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
{
try
{
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
if (!file.Exists())
{
file.ParentFile.Mkdirs();
file.CreateNewFile();
}
var outputFileUri = FileProvider.GetUriForFile(activity,
"com.x8bit.bitwarden.fileprovider", file);
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
}
catch (Java.IO.IOException) { }
}
}
var docIntent = new Intent(Intent.ActionOpenDocument);
docIntent.AddCategory(Intent.CategoryOpenable);
docIntent.SetType("*/*");
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
if (additionalIntents.Count > 0)
{
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
}
activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
return Task.FromResult(0);
}
public Task<string> DisplayPromptAync(string title = null, string description = null, public Task<string> DisplayPromptAync(string title = null, string description = null,
string text = null, string okButtonText = null, string cancelButtonText = null, string text = null, string okButtonText = null, string cancelButtonText = null,
bool numericKeyboard = false, bool autofocus = true, bool password = false) bool numericKeyboard = false, bool autofocus = true, bool password = false)
@@ -464,34 +263,6 @@ namespace Bit.Droid.Services
} }
} }
public void DisableAutofillService()
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
manager.DisableAutofillServices();
}
catch { }
}
public bool AutofillServicesEnabled()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{
// Android 5-6: Both accessibility & overlay are required or nothing happens
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
}
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
{
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
return AutofillAccessibilityServiceRunning();
}
// Android 8+: Either autofill or accessibility is required
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
}
public string GetBuildNumber() public string GetBuildNumber()
{ {
return Application.Context.ApplicationContext.PackageManager.GetPackageInfo( return Application.Context.ApplicationContext.PackageManager.GetPackageInfo(
@@ -523,25 +294,6 @@ namespace Bit.Droid.Services
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera); return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
} }
public bool SupportsAutofillService()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
return manager.IsAutofillSupported;
}
catch
{
return false;
}
}
public int SystemMajorVersion() public int SystemMajorVersion()
{ {
return (int)Build.VERSION.SdkInt; return (int)Build.VERSION.SdkInt;
@@ -632,107 +384,6 @@ namespace Bit.Droid.Services
title, cancel, destruction, buttons); title, cancel, destruction, buttons);
} }
public void Autofill(CipherView cipher)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return;
}
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
{
if (cipher == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var structure = activity.Intent.GetParcelableExtra(
AutofillManager.ExtraAssistStructure) as AssistStructure;
if (structure == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var parser = new Parser(structure, activity.ApplicationContext);
parser.Parse();
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var task = CopyTotpAsync(cipher);
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
var replyIntent = new Intent();
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent);
activity.Finish();
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
else
{
var data = new Intent();
if (cipher == null)
{
data.PutExtra("canceled", "true");
}
else
{
var task = CopyTotpAsync(cipher);
data.PutExtra("uri", cipher.Login.Uri);
data.PutExtra("username", cipher.Login.Username);
data.PutExtra("password", cipher.Login.Password);
}
if (activity.Parent == null)
{
activity.SetResult(Result.Ok, data);
}
else
{
activity.Parent.SetResult(Result.Ok, data);
}
activity.Finish();
_messagingService.Send("finishMainActivity");
if (cipher != null)
{
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
}
}
public void CloseAutofill()
{
Autofill(null);
}
public void Background()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
{
activity.SetResult(Result.Canceled);
activity.Finish();
}
else
{
activity.MoveTaskToBack(true);
}
}
public bool AutofillAccessibilityServiceRunning()
{
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
}
public bool AutofillAccessibilityOverlayPermitted()
{
return Accessibility.AccessibilityHelpers.OverlayPermitted();
}
public void OpenAccessibilityOverlayPermissionSettings() public void OpenAccessibilityOverlayPermissionSettings()
{ {
@@ -763,25 +414,6 @@ namespace Bit.Droid.Services
} }
} }
public bool AutofillServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var afm = (AutofillManager)activity.GetSystemService(
Java.Lang.Class.FromType(typeof(AutofillManager)));
return afm.IsEnabled && afm.HasEnabledAutofillServices;
}
catch
{
return false;
}
}
public void OpenAccessibilitySettings() public void OpenAccessibilitySettings()
{ {
try try
@@ -840,61 +472,6 @@ namespace Bit.Droid.Services
return true; return true;
} }
private bool DeleteDir(Java.IO.File dir)
{
if (dir != null && dir.IsDirectory)
{
var children = dir.List();
for (int i = 0; i < children.Length; i++)
{
var success = DeleteDir(new Java.IO.File(dir, children[i]));
if (!success)
{
return false;
}
}
return dir.Delete();
}
else if (dir != null && dir.IsFile)
{
return dir.Delete();
}
else
{
return false;
}
}
private bool HasPermission(string permission)
{
return ContextCompat.CheckSelfPermission(
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
}
private void AskPermission(string permission)
{
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
Constants.SelectFilePermissionRequestCode);
}
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
{
var intents = new List<IParcelable>();
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
var captureIntent = new Intent(MediaStore.ActionImageCapture);
var listCam = pm.QueryIntentActivities(captureIntent, 0);
foreach (var res in listCam)
{
var packageName = res.ActivityInfo.PackageName;
var intent = new Intent(captureIntent);
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
intent.SetPackage(packageName);
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
intents.Add(intent);
}
return intents;
}
private Intent RateIntentForUrl(string url, Activity activity) private Intent RateIntentForUrl(string url, Activity activity)
{ {
var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}")); var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}"));
@@ -912,31 +489,46 @@ namespace Bit.Droid.Services
return intent; return intent;
} }
private async Task CopyTotpAsync(CipherView cipher) public float GetSystemFontSizeScale()
{ {
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp)) var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
{ return activity?.Resources?.Configuration?.FontScale ?? 1;
var userService = ServiceContainer.Resolve<IUserService>("userService"); }
var autoCopyDisabled = await _storageService.GetAsync<bool?>(Constants.DisableAutoTotpCopyKey);
var canAccessPremium = await userService.CanAccessPremiumAsync(); public async Task OnAccountSwitchCompleteAsync()
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault()) {
{ // for any Android-specific cleanup required after switching accounts
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null)
{
CopyToClipboard(totp);
}
}
}
} }
private void CopyToClipboard(string text) public async Task SetScreenCaptureAllowedAsync()
{ {
var activity = (MainActivity)CrossCurrentActivity.Current.Activity; if (CoreHelpers.ForceScreenCaptureEnabled())
var clipboardManager = activity.GetSystemService( {
Context.ClipboardService) as Android.Content.ClipboardManager; return;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text); }
var activity = CrossCurrentActivity.Current?.Activity;
if (await _stateService.GetScreenCaptureAllowedAsync())
{
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
return;
}
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
}
public void OpenAppSettings()
{
var intent = new Intent(Android.Provider.Settings.ActionApplicationDetailsSettings);
intent.AddFlags(ActivityFlags.NewTask);
var uri = Android.Net.Uri.FromParts("package", Application.Context.PackageName, null);
intent.SetData(uri);
Application.Context.StartActivity(intent);
}
public void CloseExtensionPopUp()
{
// only used by iOS
throw new NotImplementedException();
} }
} }
} }

View File

@@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Android;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Provider;
using Android.Webkit;
using AndroidX.Core.App;
using AndroidX.Core.Content;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
{
public class FileService : IFileService
{
private readonly IStateService _stateService;
private readonly IBroadcasterService _broadcasterService;
private bool _cameraPermissionsDenied;
public FileService(IStateService stateService, IBroadcasterService broadcasterService)
{
_stateService = stateService;
_broadcasterService = broadcasterService;
_broadcasterService.Subscribe(nameof(FileService), (message) =>
{
if (message.Command == "selectFileCameraPermissionDenied")
{
_cameraPermissionsDenied = true;
}
});
}
public bool OpenFile(byte[] fileData, string id, string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(fileData, fileName);
if (intent == null)
{
return false;
}
activity.StartActivity(intent);
return true;
}
catch { }
return false;
}
public bool CanOpenFile(string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
if (intent == null)
{
return false;
}
var activities = activity.PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
catch { }
return false;
}
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return null;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
return null;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if (!file.IsFile)
{
return null;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
return intent;
}
catch { }
return null;
}
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (contentUri != null)
{
var uri = Android.Net.Uri.Parse(contentUri);
var stream = activity.ContentResolver.OpenOutputStream(uri);
// Using java bufferedOutputStream due to this issue:
// https://github.com/xamarin/xamarin-android/issues/3498
var javaStream = new Java.IO.BufferedOutputStream(stream);
javaStream.Write(fileData);
javaStream.Flush();
javaStream.Close();
return true;
}
// Prompt for location to save file
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return false;
}
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
// Unable to identify so fall back to generic "any" type
mimeType = "*/*";
}
var intent = new Intent(Intent.ActionCreateDocument);
intent.SetType(mimeType);
intent.AddCategory(Intent.CategoryOpenable);
intent.PutExtra(Intent.ExtraTitle, fileName);
activity.StartActivityForResult(intent, Core.Constants.SaveFileRequestCode);
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
}
return false;
}
public async Task ClearCacheAsync()
{
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
}
catch (Exception) { }
}
public Task SelectFileAsync()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var hasStorageWritePermission = !_cameraPermissionsDenied &&
HasPermission(Manifest.Permission.WriteExternalStorage);
var additionalIntents = new List<IParcelable>();
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
{
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
{
AskPermission(Manifest.Permission.WriteExternalStorage);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && !hasCameraPermission)
{
AskPermission(Manifest.Permission.Camera);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
{
try
{
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
if (!file.Exists())
{
file.ParentFile.Mkdirs();
file.CreateNewFile();
}
var outputFileUri = FileProvider.GetUriForFile(activity,
"com.x8bit.bitwarden.fileprovider", file);
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
}
catch (Java.IO.IOException) { }
}
}
var docIntent = new Intent(Intent.ActionOpenDocument);
docIntent.AddCategory(Intent.CategoryOpenable);
docIntent.SetType("*/*");
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
if (additionalIntents.Count > 0)
{
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
}
activity.StartActivityForResult(chooserIntent, Core.Constants.SelectFileRequestCode);
return Task.FromResult(0);
}
private bool DeleteDir(Java.IO.File dir)
{
if (dir is null)
{
return false;
}
if (dir.IsDirectory)
{
var children = dir.List();
for (int i = 0; i < children.Length; i++)
{
var success = DeleteDir(new Java.IO.File(dir, children[i]));
if (!success)
{
return false;
}
}
return dir.Delete();
}
if (dir.IsFile)
{
return dir.Delete();
}
return false;
}
private bool HasPermission(string permission)
{
return ContextCompat.CheckSelfPermission(
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
}
private void AskPermission(string permission)
{
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
Core.Constants.SelectFilePermissionRequestCode);
}
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
{
var intents = new List<IParcelable>();
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
var captureIntent = new Intent(MediaStore.ActionImageCapture);
var listCam = pm.QueryIntentActivities(captureIntent, 0);
foreach (var res in listCam)
{
var packageName = res.ActivityInfo.PackageName;
var intent = new Intent(captureIntent);
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
intent.SetPackage(packageName);
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
intents.Add(intent);
}
return intents;
}
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
@@ -25,13 +26,13 @@ namespace Bit.Droid.Services
try try
{ {
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage)); var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")"); Debug.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
ci = new CultureInfo(fallback); ci = new CultureInfo(fallback);
} }
catch (CultureNotFoundException e2) catch (CultureNotFoundException e2)
{ {
// iOS language not valid .NET culture, falling back to English // iOS language not valid .NET culture, falling back to English
Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")"); Debug.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
ci = new CultureInfo("en"); ci = new CultureInfo("en");
} }
} }
@@ -40,7 +41,7 @@ namespace Bit.Droid.Services
private string AndroidToDotnetLanguage(string androidLanguage) private string AndroidToDotnetLanguage(string androidLanguage)
{ {
Console.WriteLine("Android Language:" + androidLanguage); Debug.WriteLine("Android Language:" + androidLanguage);
var netLanguage = androidLanguage; var netLanguage = androidLanguage;
if (androidLanguage.StartsWith("zh")) if (androidLanguage.StartsWith("zh"))
{ {
@@ -79,13 +80,13 @@ namespace Bit.Droid.Services
// ONLY use cultures that have been tested and known to work // ONLY use cultures that have been tested and known to work
} }
} }
Console.WriteLine(".NET Language/Locale:" + netLanguage); Debug.WriteLine(".NET Language/Locale:" + netLanguage);
return netLanguage; return netLanguage;
} }
private string ToDotnetFallbackLanguage(PlatformCulture platCulture) private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
{ {
Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode); Debug.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually); var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
switch (platCulture.LanguageCode) switch (platCulture.LanguageCode)
{ {
@@ -95,7 +96,7 @@ namespace Bit.Droid.Services
// add more application-specific cases here (if required) // add more application-specific cases here (if required)
// ONLY use cultures that have been tested and known to work // ONLY use cultures that have been tested and known to work
} }
Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)"); Debug.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
return netLanguage; return netLanguage;
} }

View File

@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Bit.App.Services;
using Bit.Core.Abstractions;
using Bit.Core.Models;
namespace Bit.Droid.Services
{
public class WatchDeviceService : BaseWatchDeviceService
{
public WatchDeviceService(ICipherService cipherService,
IEnvironmentService environmentService,
IStateService stateService,
IVaultTimeoutService vaultTimeoutService)
: base(cipherService, environmentService, stateService, vaultTimeoutService)
{
}
protected override bool IsSupported => false;
public override bool IsConnected => false;
protected override bool CanSendData => false;
protected override Task SendDataToWatchAsync(WatchDTO watchDto) => throw new NotImplementedException();
protected override void ConnectToWatch() => throw new NotImplementedException();
}
}

View File

@@ -4,7 +4,6 @@ using Android.Content;
using Android.Runtime; using Android.Runtime;
using Android.Service.QuickSettings; using Android.Service.QuickSettings;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Accessibility; using Bit.Droid.Accessibility;
@@ -13,12 +12,12 @@ using Java.Lang;
namespace Bit.Droid.Tile namespace Bit.Droid.Tile
{ {
[Service(Permission = Manifest.Permission.BindQuickSettingsTile, Label = "@string/AutoFillTile", [Service(Permission = Manifest.Permission.BindQuickSettingsTile, Label = "@string/AutoFillTile",
Icon = "@drawable/shield")] Icon = "@drawable/shield", Exported = true)]
[IntentFilter(new string[] { ActionQsTile })] [IntentFilter(new string[] { ActionQsTile })]
[Register("com.x8bit.bitwarden.AutofillTileService")] [Register("com.x8bit.bitwarden.AutofillTileService")]
public class AutofillTileService : TileService public class AutofillTileService : TileService
{ {
private IStorageService _storageService; private IStateService _stateService;
public override void OnTileAdded() public override void OnTileAdded()
{ {
@@ -59,11 +58,11 @@ namespace Bit.Droid.Tile
private void SetTileAdded(bool isAdded) private void SetTileAdded(bool isAdded)
{ {
AccessibilityHelpers.IsAutofillTileAdded = isAdded; AccessibilityHelpers.IsAutofillTileAdded = isAdded;
if (_storageService == null) if (_stateService == null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
} }
_storageService.SaveAsync(Constants.AutofillTileAdded, isAdded); _stateService.SetAutofillTileAddedAsync(isAdded);
} }
private void ScanAndFill() private void ScanAndFill()

View File

@@ -14,7 +14,7 @@ using Java.Lang;
namespace Bit.Droid.Tile namespace Bit.Droid.Tile
{ {
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/PasswordGenerator", [Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Exported = true, Label = "@string/PasswordGenerator",
Icon = "@drawable/generate")] Icon = "@drawable/generate")]
[IntentFilter(new string[] { ActionQsTile })] [IntentFilter(new string[] { ActionQsTile })]
[Register("com.x8bit.bitwarden.GeneratorTileService")] [Register("com.x8bit.bitwarden.GeneratorTileService")]

View File

@@ -15,7 +15,8 @@ using Java.Lang;
namespace Bit.Droid.Tile namespace Bit.Droid.Tile
{ {
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/MyVault", [Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/MyVault",
Icon = "@drawable/shield")] Icon = "@drawable/shield",
Exported = true)]
[IntentFilter(new string[] { ActionQsTile })] [IntentFilter(new string[] { ActionQsTile })]
[Register("com.x8bit.bitwarden.MyVaultTileService")] [Register("com.x8bit.bitwarden.MyVaultTileService")]
public class MyVaultTileService : TileService public class MyVaultTileService : TileService

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using Android.Provider; using Android.Provider;
using Bit.App.Utilities; using Bit.App.Utilities;
@@ -47,5 +49,22 @@ namespace Bit.Droid.Utilities
await AppHelpers.SetPreconfiguredSettingsAsync(dict); await AppHelpers.SetPreconfiguredSettingsAsync(dict);
} }
} }
public static PendingIntentFlags AddPendingIntentMutabilityFlag(PendingIntentFlags pendingIntentFlags, bool isMutable)
{
//Mutable flag was added on API level 31
if (isMutable && Build.VERSION.SdkInt >= BuildVersionCodes.S)
{
return pendingIntentFlags | PendingIntentFlags.Mutable;
}
//Immutable flag was added on API level 23
if (!isMutable && Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
return pendingIntentFlags | PendingIntentFlags.Immutable;
}
return pendingIntentFlags;
}
} }
} }

View File

@@ -1,58 +0,0 @@
#if !FDROID
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Crashes;
using Newtonsoft.Json;
namespace Bit.Droid.Utilities
{
public class AppCenterHelper
{
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
private readonly IAppIdService _appIdService;
private readonly IUserService _userService;
private string _userId;
private string _appId;
public AppCenterHelper(
IAppIdService appIdService,
IUserService userService)
{
_appIdService = appIdService;
_userService = userService;
}
public async Task InitAsync()
{
_userId = await _userService.GetUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
AppCenter.Start(AppSecret, typeof(Crashes));
AppCenter.SetUserId(_userId);
Crashes.GetErrorAttachments = (ErrorReport report) =>
{
return new ErrorAttachmentLog[]
{
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
};
};
}
public string Description
{
get
{
return JsonConvert.SerializeObject(new
{
AppId = _appId,
UserId = _userId
}, Formatting.Indented);
}
}
}
}
#endif

View File

@@ -59,10 +59,10 @@ namespace Bit.Droid.Utilities
{ {
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled) if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
{ {
theme = "dark"; theme = ThemeManager.Dark;
} }
if (theme == "dark" || theme == "black" || theme == "nord") if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord || theme == ThemeManager.SolarizedDark)
{ {
LightTheme = false; LightTheme = false;
} }

View File

@@ -7,7 +7,8 @@ namespace Bit.Droid
{ {
[Activity( [Activity(
NoHistory = true, NoHistory = true,
LaunchMode = LaunchMode.SingleTop)] LaunchMode = LaunchMode.SingleTop,
Exported = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionView }, [IntentFilter(new[] { Android.Content.Intent.ActionView },
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable }, Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
DataScheme = "bitwarden")] DataScheme = "bitwarden")]

Binary file not shown.

Binary file not shown.

BIN
src/Android/lib/x86/libargon2.so Executable file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
namespace Bit.App.Abstractions
{
public interface IAccountsManager
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task LogOutAsync(string userId, bool userInitiated, bool expired);
Task PromptToSwitchToExistingAccountAsync(string userId);
}
}

View File

@@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
public interface INavigationParams { }
public interface IAccountsManagerHost
{
Task SetPreviousPageInfoAsync();
void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
Task UpdateThemeAsync();
}
}

View File

@@ -1,6 +1,6 @@
using Bit.Core.Enums; using System.Threading.Tasks;
using Bit.Core.Models.View; using Bit.Core.Enums;
using System.Threading.Tasks; using Bit.Core.Models;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
@@ -8,42 +8,36 @@ namespace Bit.App.Abstractions
{ {
string DeviceUserAgent { get; } string DeviceUserAgent { get; }
DeviceType DeviceType { get; } DeviceType DeviceType { get; }
int SystemMajorVersion();
string SystemModel();
string GetBuildNumber();
void Toast(string text, bool longDuration = false); void Toast(string text, bool longDuration = false);
bool LaunchApp(string appName);
Task ShowLoadingAsync(string text); Task ShowLoadingAsync(string text);
Task HideLoadingAsync(); Task HideLoadingAsync();
bool OpenFile(byte[] fileData, string id, string fileName);
bool SaveFile(byte[] fileData, string id, string fileName, string contentUri);
bool CanOpenFile(string fileName);
Task ClearCacheAsync();
Task SelectFileAsync();
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null, Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false, string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true, bool password = false); bool autofocus = true, bool password = false);
void RateApp(); Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
bool SupportsFaceBiometric(); bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync(); Task<bool> SupportsFaceBiometricAsync();
bool SupportsNfc(); bool SupportsNfc();
bool SupportsCamera(); bool SupportsCamera();
bool SupportsAutofillService(); bool SupportsFido2();
int SystemMajorVersion();
string SystemModel(); bool LaunchApp(string appName);
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons); void RateApp();
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
void Autofill(CipherView cipher);
void CloseAutofill();
void Background();
bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted();
bool AutofillServiceEnabled();
void DisableAutofillService();
bool AutofillServicesEnabled();
string GetBuildNumber();
void OpenAccessibilitySettings(); void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings(); void OpenAccessibilityOverlayPermissionSettings();
void OpenAutofillSettings(); void OpenAutofillSettings();
long GetActiveTime(); long GetActiveTime();
void CloseMainApp(); void CloseMainApp();
bool SupportsFido2(); float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync();
Task SetScreenCaptureAllowedAsync();
void OpenAppSettings();
void CloseExtensionPopUp();
} }
} }

View File

@@ -7,7 +7,7 @@ namespace Bit.App.Abstractions
string[] ProtectedFields { get; } string[] ProtectedFields { get; }
Task<bool> ShowPasswordPromptAsync(); Task<bool> ShowPasswordPromptAsync();
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync(); Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
Task<bool> Enabled(); Task<bool> Enabled();

View File

@@ -1,5 +1,6 @@
using Newtonsoft.Json.Linq; using System.Threading.Tasks;
using System.Threading.Tasks; using Bit.App.Models;
using Newtonsoft.Json.Linq;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
@@ -9,6 +10,8 @@ namespace Bit.App.Abstractions
Task OnRegisteredAsync(string token, string device); Task OnRegisteredAsync(string token, string device);
void OnUnregistered(string device); void OnUnregistered(string device);
void OnError(string message, string device); void OnError(string message, string device);
Task OnNotificationTapped(BaseNotificationData data);
Task OnNotificationDismissed(BaseNotificationData data);
bool ShouldShowNotification(); bool ShouldShowNotification();
} }
} }

View File

@@ -1,12 +1,17 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
public interface IPushNotificationService public interface IPushNotificationService
{ {
bool IsRegisteredForPush { get; } bool IsRegisteredForPush { get; }
Task<bool> AreNotificationsSettingsEnabledAsync();
Task<string> GetTokenAsync(); Task<string> GetTokenAsync();
Task RegisterAsync(); Task RegisterAsync();
Task UnregisterAsync(); Task UnregisterAsync();
void SendLocalNotification(string title, string message, BaseNotificationData data);
void DismissLocalNotification(string notificationId);
} }
} }

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Bit.App</RootNamespace> <RootNamespace>Bit.App</RootNamespace>
<AssemblyName>BitwardenApp</AssemblyName> <AssemblyName>BitwardenApp</AssemblyName>
<Configurations>Debug;Release;FDroid</Configurations> <Configurations>Debug;Release;FDroid</Configurations>
@@ -13,14 +13,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" /> <PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" /> <PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.2" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" /> <PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.5" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" /> <PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -97,11 +97,11 @@
<Compile Update="Pages\Vault\PasswordHistoryPage.xaml.cs"> <Compile Update="Pages\Vault\PasswordHistoryPage.xaml.cs">
<DependentUpon>PasswordHistoryPage.xaml</DependentUpon> <DependentUpon>PasswordHistoryPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Vault\AddEditPage.xaml.cs"> <Compile Update="Pages\Vault\CipherDetailsPage.xaml.cs">
<DependentUpon>AddEditPage.xaml</DependentUpon> <DependentUpon>CipherDetailsPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Vault\ViewPage.xaml.cs"> <Compile Update="Pages\Vault\CipherAddEditPage.xaml.cs">
<DependentUpon>ViewPage.xaml</DependentUpon> <DependentUpon>CipherAddEditPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Settings\SettingsPage\SettingsPage.xaml.cs"> <Compile Update="Pages\Settings\SettingsPage\SettingsPage.xaml.cs">
<DependentUpon>SettingsPage.xaml</DependentUpon> <DependentUpon>SettingsPage.xaml</DependentUpon>
@@ -121,17 +121,33 @@
<DependentUpon>SendGroupingsPage.xaml</DependentUpon> <DependentUpon>SendGroupingsPage.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
<Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs">
<DependentUpon>LoginPasswordlessPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Accounts\LoginPasswordlessRequestPage.xaml.cs">
<DependentUpon>LoginPasswordlessRequestPage.xaml</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources\" /> <Folder Include="Resources\" />
<Folder Include="Behaviors\" /> <Folder Include="Behaviors\" />
<Folder Include="Lists\" />
<Folder Include="Lists\ItemLayouts\" />
<Folder Include="Lists\DataTemplateSelectors\" />
<Folder Include="Lists\ItemLayouts\CustomFields\" />
<Folder Include="Lists\ItemViewModels\" />
<Folder Include="Lists\ItemViewModels\CustomFields\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
<Folder Include="Controls\DateTime\" />
<Folder Include="Controls\IconLabelButton\" />
<Folder Include="Controls\PasswordStrengthProgressBar\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml"> <EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -158,12 +174,6 @@
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Styles\Base.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Resources\AppResources.cs.Designer.cs"> <Compile Update="Resources\AppResources.cs.Designer.cs">
<DependentUpon>AppResources.cs.resx</DependentUpon> <DependentUpon>AppResources.cs.resx</DependentUpon>
@@ -416,5 +426,16 @@
<ItemGroup> <ItemGroup>
<None Remove="Behaviors\" /> <None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" /> <None Remove="Xamarin.CommunityToolkit" />
<None Remove="Lists\" />
<None Remove="Lists\DataTemplates\" />
<None Remove="Lists\DataTemplateSelectors\" />
<None Remove="Lists\DataTemplates\CustomFields\" />
<None Remove="Lists\ItemViewModels\" />
<None Remove="Lists\ItemViewModels\CustomFields\" />
<None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" />
<None Remove="Controls\IconLabelButton\" />
<None Remove="Controls\PasswordStrengthProgressBar\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,36 +1,42 @@
using Bit.App.Abstractions; using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Xaml; using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)] [assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App namespace Bit.App
{ {
public partial class App : Application public partial class App : Application, IAccountsManagerHost
{ {
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IFileService _fileService;
private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService;
private static bool _isResumed; private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests;
private static object _processingLoginRequestLock = new object();
public App(AppOptions appOptions) public App(AppOptions appOptions)
{ {
@@ -40,140 +46,223 @@ namespace Bit.App
Current = this; Current = this;
return; return;
} }
_userService = ServiceContainer.Resolve<IUserService>("userService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_authService = ServiceContainer.Resolve<IAuthService>("authService"); _authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_fileService = ServiceContainer.Resolve<IFileService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_accountsManager.Init(() => Options, this);
Bootstrap(); Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) => _broadcasterService.Subscribe(nameof(App), async (message) =>
{ {
if (message.Command == "showDialog") try
{ {
var details = message.Data as DialogDetails; if (message.Command == "showDialog")
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () =>
{ {
if (!string.IsNullOrWhiteSpace(details.CancelText)) var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () =>
{ {
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText, if (!string.IsNullOrWhiteSpace(details.CancelText))
details.CancelText); {
} confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
else details.CancelText);
{ }
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText); else
} {
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}); }
} _messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
else if (message.Command == "locked") });
{ }
await LockedAsync(!(message.Data as bool?).GetValueOrDefault()); else if (message.Command == "resumed")
}
else if (message.Command == "lockVault")
{
await _vaultTimeoutService.LockAsync(true);
}
else if (message.Command == "logout")
{
Device.BeginInvokeOnMainThread(async () =>
await LogOutAsync((message.Data as bool?).GetValueOrDefault()));
}
else if (message.Command == "loggedOut")
{
// Clean up old migrated key if they ever log out.
await _secureStorageService.RemoveAsync("oldKey");
}
else if (message.Command == "resumed")
{
if (Device.RuntimePlatform == Device.iOS)
{ {
ResumedAsync(); if (Device.RuntimePlatform == Device.iOS)
{
ResumedAsync().FireAndForget();
}
}
else if (message.Command == "slept")
{
if (Device.RuntimePlatform == Device.iOS)
{
await SleptAsync();
}
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
} }
} }
else if (message.Command == "slept") catch (Exception ex)
{ {
if (Device.RuntimePlatform == Device.iOS) LoggerHelper.LogEvenIfCantBeResolved(ex);
{
await SleptAsync();
}
} }
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await SetMainPageAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
}); });
} }
private async Task CheckPasswordlessLoginRequestsAsync()
{
if (!_isResumed)
{
_pendingCheckPasswordlessLoginRequests = true;
return;
}
_pendingCheckPasswordlessLoginRequests = false;
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
if (notification == null)
{
return;
}
if (await CheckShouldSwitchActiveUserAsync(notification))
{
return;
}
// Delay to wait for the vault page to appear
await Task.Delay(2000);
// if there is a request modal opened ignore all incoming requests
if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
{
return;
}
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
{
PubKey = loginRequestData.PublicKey,
Id = loginRequestData.Id,
IpAddress = loginRequestData.RequestIpAddress,
Email = await _stateService.GetEmailAsync(),
FingerprintPhrase = loginRequestData.RequestFingerprint,
RequestDate = loginRequestData.CreationDate,
DeviceType = loginRequestData.RequestDeviceType,
Origin = loginRequestData.Origin
});
await _stateService.SetPasswordlessLoginNotificationAsync(null);
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
if (!loginRequestData.IsExpired)
{
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
}
}
private async Task<bool> CheckShouldSwitchActiveUserAsync(PasswordlessRequestNotification notification)
{
var activeUserId = await _stateService.GetActiveUserIdAsync();
if (notification.UserId == activeUserId)
{
return false;
}
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
if (result == AppResources.Ok)
{
await _stateService.SetActiveUserAsync(notification.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
return true;
}
public AppOptions Options { get; private set; } public AppOptions Options { get; private set; }
protected async override void OnStart() protected async override void OnStart()
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnStart"); System.Diagnostics.Debug.WriteLine("XF App: OnStart");
_isResumed = true;
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
Prime(); Prime();
if (string.IsNullOrWhiteSpace(Options.Uri)) if (string.IsNullOrWhiteSpace(Options.Uri))
{ {
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_storageService); _stateService);
if (!updated) if (!updated)
{ {
SyncIfNeeded(); SyncIfNeeded();
} }
} }
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
@@ -192,9 +281,12 @@ namespace Bit.App
var isLocked = await _vaultTimeoutService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
{ {
await _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime()); await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
if (!SetTabsPageFromAutofill(isLocked))
{
ClearAutofillUri();
} }
SetTabsPageFromAutofill(isLocked);
await SleptAsync(); await SleptAsync();
} }
} }
@@ -203,9 +295,13 @@ namespace Bit.App
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnResume"); System.Diagnostics.Debug.WriteLine("XF App: OnResume");
_isResumed = true; _isResumed = true;
if (_pendingCheckPasswordlessLoginRequests)
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
ResumedAsync(); ResumedAsync().FireAndForget();
} }
} }
@@ -215,12 +311,12 @@ namespace Bit.App
_messagingService.Send("stopEventTimer"); _messagingService.Send("stopEventTimer");
} }
private async void ResumedAsync() private async Task ResumedAsync()
{ {
await UpdateThemeAsync(); await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
await UpdateThemeAsync();
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
Prime(); Prime();
SyncIfNeeded(); SyncIfNeeded();
@@ -234,7 +330,7 @@ namespace Bit.App
{ {
await Device.InvokeOnMainThreadAsync(() => await Device.InvokeOnMainThreadAsync(() =>
{ {
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); ThemeManager.SetTheme(Current.Resources);
_messagingService.Send("updatedTheme"); _messagingService.Send("updatedTheme");
}); });
} }
@@ -247,61 +343,24 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar(); new System.Globalization.UmAlQuraCalendar();
} }
private async Task LogOutAsync(bool expired)
{
await AppHelpers.LogOutAsync();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task SetMainPageAsync()
{
var authed = await _userService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLockedAsync())
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
}
else if (Options.Uri != null)
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else
{
Current.MainPage = new TabsPage(Options);
}
}
else
{
Current.MainPage = new HomePage(Options);
}
}
private async Task ClearCacheIfNeededAsync() private async Task ClearCacheIfNeededAsync()
{ {
var lastClear = await _storageService.GetAsync<DateTime?>(Constants.LastFileCacheClearKey); var lastClear = await _stateService.GetLastFileCacheClearAsync();
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1) if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
{ {
var task = Task.Run(() => _deviceActionService.ClearCacheAsync()); var task = Task.Run(() => _fileService.ClearCacheAsync());
} }
} }
private void SetTabsPageFromAutofill(bool isLocked) private void ClearAutofillUri()
{
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
{
Options.Uri = null;
}
}
private bool SetTabsPageFromAutofill(bool isLocked)
{ {
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) && if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!Options.FromAutofillFramework) !Options.FromAutofillFramework)
@@ -321,7 +380,9 @@ namespace Bit.App
} }
}); });
}); });
return true;
} }
return false;
} }
private void Prime() private void Prime()
@@ -337,13 +398,13 @@ namespace Bit.App
{ {
InitializeComponent(); InitializeComponent();
SetCulture(); SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); ThemeManager.SetTheme(Current.Resources);
Current.RequestedThemeChanged += (s, a) => Current.RequestedThemeChanged += (s, a) =>
{ {
UpdateThemeAsync(); UpdateThemeAsync();
}; };
Current.MainPage = new HomePage(); Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = SetMainPageAsync(); _accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
} }
@@ -364,44 +425,71 @@ namespace Bit.App
}); });
} }
private async Task LockedAsync(bool autoPromptBiometric) public async Task SetPreviousPageInfoAsync()
{ {
await _stateService.PurgeAsync();
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
}
}
PreviousPageInfo lastPageBeforeLock = null; PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0) if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{ {
var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1]; var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
if (topPage is NavigationPage navPage) if (topPage is NavigationPage navPage)
{ {
if (navPage.CurrentPage is ViewPage viewPage) if (navPage.CurrentPage is CipherDetailsPage cipherDetailsPage)
{ {
lastPageBeforeLock = new PreviousPageInfo lastPageBeforeLock = new PreviousPageInfo
{ {
Page = "view", Page = "view",
CipherId = viewPage.ViewModel.CipherId CipherId = cipherDetailsPage.ViewModel.CipherId
}; };
} }
else if (navPage.CurrentPage is AddEditPage addEditPage && addEditPage.ViewModel.EditMode) else if (navPage.CurrentPage is CipherAddEditPage cipherAddEditPage && cipherAddEditPage.ViewModel.EditMode)
{ {
lastPageBeforeLock = new PreviousPageInfo lastPageBeforeLock = new PreviousPageInfo
{ {
Page = "edit", Page = "edit",
CipherId = addEditPage.ViewModel.CipherId CipherId = cipherAddEditPage.ViewModel.CipherId
}; };
} }
} }
} }
await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock); await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
var lockPage = new LockPage(Options, autoPromptBiometric); }
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
{
switch (navTarget)
{
case NavigationTarget.HomeLogin:
Current.MainPage = new NavigationPage(new HomePage(Options));
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
{
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
}
break;
case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams)
{
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
break;
case NavigationTarget.Home:
Current.MainPage = new TabsPage(Options);
break;
case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
break;
case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
break;
}
} }
} }
} }

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
x:Name="_mainOverlay"
x:DataType="controls:AccountSwitchingOverlayViewModel"
x:Class="Bit.App.Controls.AccountSwitchingOverlayView"
BackgroundColor="#22000000"
Padding="0"
IsVisible="False">
<StackLayout
x:Name="_accountListContainer"
VerticalOptions="Fill"
HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent">
<Frame
Padding="0"
HorizontalOptions="Fill"
VerticalOptions="Start"
xct:ShadowEffect.Color="Black"
xct:ShadowEffect.Radius="10"
xct:ShadowEffect.OffsetY="3">
<ListView
x:Name="_accountListView"
ItemsSource="{Binding BindingContext.AccountViews, Source={x:Reference _mainOverlay}}"
BackgroundColor="{DynamicResource BackgroundColor}"
VerticalOptions="Start"
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}"
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Effects>
<effects:ScrollViewContentInsetAdjustmentBehaviorEffect />
</ListView.Effects>
</ListView>
</Frame>
<BoxView
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="FillAndExpand">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
</BoxView.GestureRecognizers>
</BoxView>
</StackLayout>
</ContentView>

View File

@@ -0,0 +1,194 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AccountSwitchingOverlayView : ContentView
{
public static readonly BindableProperty MainPageProperty = BindableProperty.Create(
nameof(MainPage),
typeof(ContentPage),
typeof(AccountSwitchingOverlayView),
defaultBindingMode: BindingMode.OneWay);
public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
nameof(MainFab),
typeof(View),
typeof(AccountSwitchingOverlayView),
defaultBindingMode: BindingMode.OneWay);
public ContentPage MainPage
{
get => (ContentPage)GetValue(MainPageProperty);
set => SetValue(MainPageProperty, value);
}
public View MainFab
{
get => (View)GetValue(MainFabProperty);
set => SetValue(MainFabProperty, value);
}
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public AccountSwitchingOverlayView()
{
InitializeComponent();
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<AccountViewCellViewModel>(LongPressAccountAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
}
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
public ICommand ToggleVisibililtyCommand { get; }
public ICommand SelectAccountCommand { get; }
public ICommand LongPressAccountCommand { get; }
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
public bool LongPressAccountEnabled { get; set; } = true;
public Action AfterHide { get; set; }
public async Task ToggleVisibilityAsync()
{
if (IsVisible)
{
await HideAsync();
}
else
{
await ShowAsync();
}
}
public async Task ShowAsync()
{
if (ViewModel == null)
{
return;
}
await ViewModel.RefreshAccountViewsAsync();
await Device.InvokeOnMainThreadAsync(async () =>
{
// start listView in default (off-screen) position
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
// re-measure in case accounts have been removed without changing screens
if (ViewModel.AccountViews != null)
{
_accountListView.HeightRequest = AccountListRowHeight * ViewModel.AccountViews.Count;
}
// set overlay opacity to zero before making visible and start fade-in
Opacity = 0;
IsVisible = true;
this.FadeTo(1, 100);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-out
MainFab.FadeTo(0, 200);
}
// slide account list into view
await _accountListContainer.TranslateTo(0, 0, 200, Easing.SinOut);
});
}
public async Task HideAsync()
{
if (!IsVisible)
{
// already hidden, don't animate again
return;
}
// Not all animations are awaited. This is intentional to allow multiple simultaneous animations.
await Device.InvokeOnMainThreadAsync(async () =>
{
// start overlay fade-out
this.FadeTo(0, 200);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-in
MainFab.FadeTo(1, 200);
}
// slide account list out of view
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 200, Easing.SinIn);
// remove overlay
IsVisible = false;
AfterHide?.Invoke();
});
}
private async void FreeSpaceOverlay_Tapped(object sender, EventArgs e)
{
try
{
await HideAsync();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
try
{
await Task.Delay(100);
await HideAsync();
ViewModel?.SelectAccountCommand?.Execute(item);
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{
if (!LongPressAccountEnabled || !item.IsAccount)
{
return;
}
try
{
await Task.Delay(100);
await HideAsync();
ViewModel?.LongPressAccountCommand?.Execute(
new Tuple<ContentPage, AccountViewCellViewModel>(MainPage, item));
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AccountSwitchingOverlayViewModel : ExtendedViewModel
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
public AccountSwitchingOverlayViewModel(IStateService stateService,
IMessagingService messagingService,
ILogger logger)
{
_stateService = stateService;
_messagingService = messagingService;
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
}
// this needs to be a new list every time for the binding to get updated,
// XF doesn't currentlyl provide a direct way to update on same instance
// https://github.com/xamarin/Xamarin.Forms/issues/1950
public List<AccountView> AccountViews => _stateService?.AccountViews is null ? null : new List<AccountView>(_stateService.AccountViews);
public bool AllowActiveAccountSelection { get; set; }
public bool AllowAddAccountRow { get; set; }
public ICommand SelectAccountCommand { get; }
public ICommand LongPressAccountCommand { get; }
public bool FromIOSExtension { get; set; }
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
if (!item.AccountView.IsAccount)
{
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
return;
}
if (!item.AccountView.IsActive)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
if (FromIOSExtension)
{
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
}
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
}
private async Task LongPressAccountAsync(Tuple<ContentPage, AccountViewCellViewModel> item)
{
var (page, account) = item;
if (account.AccountView.IsAccount)
{
await AppHelpers.AccountListOptions(page, account);
}
}
public async Task RefreshAccountViewsAsync()
{
await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(AccountViews)));
}
}
}

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