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

Compare commits

..

286 Commits

Author SHA1 Message Date
Andreas Coroiu
da051b8fc1 Add example of delegated UI class 2024-02-19 16:44:15 +01:00
Andreas Coroiu
c801b2fc3a Fix incompatible GUID conversions 2024-02-19 15:35:17 +01:00
Federico Maccaroni
41161864db PM-5154 [Passkeys iOS] Fix Credential ID handling on bytes and string formats. Fix Discoverable to be lowercase on set so it doesn't break parsing on clients. Added UserDisplayName on Fido2 entities. Extracted the Guid Standard/Raw format helpers to a extensions class. 2024-02-16 19:30:33 -03:00
Andreas Coroiu
a1c9ebf01f fix wrong signature format 2024-02-16 10:57:44 +01:00
Federico Maccaroni
7381d5278a PM-5154 Fixed select passkey flow and started implementing create passkey on iOS 2024-02-15 21:16:54 -03:00
Federico Maccaroni
8b5a7b257d Merge branch 'PM-5731-create-c-web-authn-authenticator-to-support-maui-apps' into temp-passkeys-ios-fede 2024-02-15 21:09:09 -03:00
mpbw2
62e0626648 Merge branch 'feature/maui-migration-passkeys' into PM-5731-create-c-web-authn-authenticator-to-support-maui-apps 2024-02-15 14:42:53 -05:00
Federico Maccaroni
d339514d9a Merge branch 'main' into feature/maui-migration-passkeys
# Conflicts:
#	.github/workflows/build.yml
#	nuget.config
#	src/App/App.csproj
#	src/App/Platforms/Android/AndroidManifest.xml
#	src/App/Platforms/iOS/AppDelegate.cs
#	src/Core/Core.csproj
#	src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs
#	src/Core/Utilities/ThemeManager.cs
#	src/iOS.Autofill/CredentialProviderViewController.cs
#	src/iOS.Autofill/iOS.Autofill.csproj
2024-02-14 14:19:51 -03:00
Federico Maccaroni
75ec96f282 PM-3349 Added exception on gitignore so the nupkg for the AndroidX Credentials is added 2024-02-14 13:58:35 -03:00
Andreas Coroiu
3c848a3dcc [PM-5731] feat: implement credential assertion in client 2024-02-12 13:45:41 +01:00
Federico Maccaroni
18beb4e5f4 Added iOS passkeys integration, warning this branch has lots of logs to ease "debugging" extensions. 2024-02-09 18:15:25 -03:00
Federico Maccaroni
e9d1792dd7 Merge branch 'feature/maui-migration-passkeys' into PM-5731-create-c-web-authn-authenticator-to-support-maui-apps 2024-02-08 12:33:33 -03:00
Federico Maccaroni
8f8a5795d3 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-02-08 12:33:16 -03:00
Federico Maccaroni
4631a9e62c PM-3349 PM-3350 Fixed Unit tests because of referencing FFImageLoading when it's not possible 2024-02-08 12:31:52 -03:00
Federico Maccaroni
51ee6a84b5 Merged main into maui-migration 2024-02-08 12:17:34 -03:00
Andreas Coroiu
b8c5ef501a [PM-5731] feat: implement fido2 client createCredential 2024-02-08 14:41:08 +01:00
Andreas Coroiu
2075f8168c [PM-5731] chore: document uri helpers 2024-02-08 14:41:07 +01:00
Andreas Coroiu
0f5df0f6b0 [PM-5731] feat: add incomplete rpId verification 2024-02-08 14:41:07 +01:00
Andreas Coroiu
ad8faec200 [PM-5731] feat: add sameOriginWithAncestor and user id length checks 2024-02-08 14:41:07 +01:00
Andreas Coroiu
3223ceb9a8 [PM-5731] fix: failing test 2024-02-08 14:41:06 +01:00
Dinis Vieira
8d5006c0bd [PM-5906] Fix for incorrect Send MaxAccess white text color on label when using light theme on iOS (#2981)
* PM-5906 workaround for incorrect textcolor when programmatically changing text on Entry

* Update src/Core/Pages/Send/SendAddEditPage.xaml.cs

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2024-02-07 19:09:18 +00:00
Dinis Vieira
37208571fe [PM-5907] Fix for incorrect TOTP white text color on label when using light theme on iOS (#2982)
* PM-5907 workaround for incorrect textcolor when programmatically changing text on Entry

* Update src/Core/Pages/Vault/CipherAddEditPage.xaml.cs

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2024-02-07 19:08:51 +00:00
Federico Maccaroni
7469dad35a PM-5731 Fix build updating discoverable flag 2024-02-07 15:31:21 -03:00
Dinis Vieira
759627b3c7 PM-6077 Separated Android and iOS HybridWebViewHandler so that it can be used on iOS.Core (#2983) 2024-02-07 17:50:07 +00:00
Federico Maccaroni
eae84ded42 Merge branch 'feature/maui-migration-passkeys' into PM-5731-create-c-web-authn-authenticator-to-support-maui-apps 2024-02-07 14:00:00 -03:00
Federico Maccaroni
563210a74e Merge branch 'PM-5731-create-c-web-authn-authenticator-to-support-maui-apps' of https://github.com/bitwarden/mobile into PM-5731-create-c-web-authn-authenticator-to-support-maui-apps 2024-02-07 13:59:53 -03:00
Federico Maccaroni
08fac4752f PM-3349 PM-3350 Fix crash on iOS AppGroup container URL because of sln config on AndroidX Credentials. Changed the project reference of Credentials to be a local NuGet. 2024-02-07 13:59:31 -03:00
Andreas Coroiu
70db27b750 [PM-5731] feat: scaffold fido2 client 2024-02-06 14:20:52 +01:00
Andreas Coroiu
00cff182b4 [PM-5731] chore: add user presence todo comment 2024-02-06 08:38:38 +01:00
Federico Maccaroni
dc5e90436b Merge branch 'feature/maui-migration-passkeys' into PM-5731-create-c-web-authn-authenticator-to-support-maui-apps 2024-02-05 14:32:00 -03:00
Federico Maccaroni
9307e7e0d8 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-02-05 14:31:44 -03:00
Federico Maccaroni
b1a0801f9b PM-3349 Removed commented code from build.yml regarding FDroid that is not needed anymore. 2024-02-05 13:14:37 -03:00
Federico Maccaroni
04cc53b934 Merge branch 'main' into feature/maui-migration 2024-02-05 11:50:41 -03:00
Federico Maccaroni
c138658a31 [PM-5896] Fix MAUI iOS Background crash due to lock files on suspension (#2969)
* PM-5896 Fix background crash on iOS due to lock files when app gets suspended. Changed loading and error placeholders of the CachedImage to not be used and use default icon of IconLabel instead changing visibility.

* PM-5896 Changed methods to be protected so that they don't get removed by the linker.

* PM-5896 Added stub class and references to it so to have stronger references to Icon_Success and Icon_Error so the linker doesn't remove them.
2024-02-05 11:46:08 -03:00
Dinis Vieira
f1854f2c04 PM-5903 Changed App.xaml.cs SetOption to only update the needed properties instead of replacing the existing Options object which would cause the AccountSwitcher button bug (#2973) 2024-02-03 15:59:22 +00:00
Federico Maccaroni
e4056d9ee6 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-02-02 15:51:50 -03:00
Federico Maccaroni
eb95a54db2 PM-3350 Fix iossimulator-x64 argon2id load so we can test on simulators and also made easier to maintain loading the argon2id library on the iOS projects by setting a general Directory.Build.props that is shared. 2024-02-02 15:48:04 -03:00
Dinis Vieira
7ddea4c70b PM-5902 fix for account switcher not dismissing when tapping outside (#2974) 2024-02-01 21:44:13 +00:00
Federico Maccaroni
3804e86995 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-02-01 12:35:50 -03:00
Andreas Coroiu
b787a6c840 [PM-5731] chore: clean up and refactor attestation tests 2024-02-01 13:57:38 +01:00
Andreas Coroiu
a6c4bc9273 [PM-5731] chore: clean up and refactor assertion tests 2024-02-01 09:23:30 +01:00
Federico Maccaroni
b23bed182f Merge branch 'main' into feature/maui-migration 2024-01-31 12:14:02 -03:00
Federico Maccaroni
f8e421871b Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2024-01-31 12:13:31 -03:00
Andreas Coroiu
4988dbea72 [PM-5731] feat: ensure unlocked vault 2024-01-30 14:40:48 +01:00
Andreas Coroiu
ca250c53ad [PM-5731] feat: add support for specifying user presence requirement 2024-01-30 14:19:41 +01:00
Andreas Coroiu
6bb724ff06 [PM-5731] feat: add support for silent discoverability 2024-01-30 13:10:09 +01:00
Andreas Coroiu
f3c64a89eb [PM-5731] chore: add Async to method names 2024-01-30 10:15:58 +01:00
Andreas Coroiu
aa71ebc634 [PM-5731] chore: use primary constructor 2024-01-30 10:14:08 +01:00
Andreas Coroiu
d0bb7f0a54 [PM-5731] feat: remove logging 2024-01-30 10:01:11 +01:00
mpbw2
d0103496b9 [PM-5910] Workaround for for sliding elements in Duo 2FA flow (#2967)
* workaround for sliding elements in duo 2fa flow

* restrict workaround to Android

* restrict workaround to Android

* Revert "restrict workaround to Android"

This reverts commit c2753d5dc4.

* Revert "restrict workaround to Android"

This reverts commit 69688cfb98.
2024-01-29 17:26:14 -05:00
Federico Maccaroni
cd8952221e Merge branch 'main' into feature/maui-migration 2024-01-29 17:17:07 -03:00
Federico Maccaroni
155c7539bd Removing error/loading placeholders of icons on the cells to see if that is causing the background crash on iOS; so we can test this in TestFlight 2024-01-29 17:16:01 -03:00
Federico Maccaroni
5f43681fb1 PM-3350 Go back to include argon2id and interpreter 2024-01-29 14:30:42 -03:00
Federico Maccaroni
d2965e6e10 PM-3350 Enabled iOS extensions and WatchOS app to be included based on the Directory.Build.props 2024-01-29 14:01:18 -03:00
Federico Maccaroni
ec1ade7761 Merge branch 'main' into feature/maui-migration
# Conflicts:
#	src/App/Platforms/Android/AndroidManifest.xml
2024-01-29 13:58:30 -03:00
Federico Maccaroni
f35bef0d7b PM-3350 Go back to use Interpreter and added some Directory.Build.props to easily change Codesign properties and also include/exclude iOS extensions / WatchOS from the build. 2024-01-29 13:56:31 -03:00
Andreas Coroiu
5d5d113369 [PM-5731] feat: implement signing 2024-01-29 14:43:14 +01:00
Andreas Coroiu
7ca9e61e93 [PM-5731] feat: return public key in DER format 2024-01-29 13:50:30 +01:00
Andreas Coroiu
da7326b0cc [PM-5731] feat: implement key generation 2024-01-29 11:27:24 +01:00
Federico Maccaroni
138d37cf5e PM-5928 Fix circle animation to be shown on verification codes list on each item 2024-01-26 12:27:57 -03:00
Andreas Coroiu
c87728027e [PM-5731] feat: partial attestation implementation 2024-01-26 14:57:44 +01:00
Federico Maccaroni
fc2fed079f PM-3350 Try to disable Interpreter to have better crash knowledge. This time testing if avoiding loading the argon2id lib we're able to not use the interpreter. 2024-01-26 10:34:28 -03:00
Federico Maccaroni
9c441a98f4 Merge branch 'main' into feature/maui-migration 2024-01-26 10:17:21 -03:00
Andreas Coroiu
e1908d8eef [PM-5731] chore: clean up unusued params 2024-01-26 10:45:23 +01:00
Andreas Coroiu
8be604feac [PM-5731] feat: add unknown error handling 2024-01-26 10:44:39 +01:00
Andreas Coroiu
c90ed74faa [PM-5731] feat: add user verification checks 2024-01-26 10:30:31 +01:00
Federico Maccaroni
1491872b62 PM-3350 Added check for state migration version before trying to migrate LiteDB values into Prefs when there's no need to and that may be inducing crashes on backgrounded iOS apps. 2024-01-25 17:56:28 -03:00
Federico Maccaroni
c74636ffa5 PM-3350 Commented event collection upload on the timer and when sending the app to background to see if that prevents the app from crashing on release mode. 2024-01-25 13:22:42 -03:00
Federico Maccaroni
05677f93c5 Fix merge from main 2024-01-25 13:20:13 -03:00
Federico Maccaroni
0aef241df6 Merge branch 'main' into feature/maui-migration
# Conflicts:
#	src/Core/Pages/Settings/AboutSettingsPageViewModel.cs
2024-01-25 13:16:42 -03:00
Andreas Coroiu
32c43afae2 [PM-5731] feat: implement credential creation 2024-01-25 16:29:26 +01:00
Andreas Coroiu
44b2443554 [PM-5731] feat: add new credential confirmaiton 2024-01-25 10:49:23 +01:00
Federico Maccaroni
e0b58461b5 Merge branch 'main' into feature/maui-migration 2024-01-24 11:22:28 -03:00
Federico Maccaroni
cd33c7f608 PM-3350 Added new style to prevent spell check and text prediction 2024-01-24 11:22:02 -03:00
Andreas Coroiu
f0dde7eb82 [PM-5731] feat: implement credential exclusion 2024-01-24 14:18:27 +01:00
Andreas Coroiu
19639b61c3 [PM-5731] feat: start implementing attestation 2024-01-24 11:04:37 +01:00
Federico Maccaroni
9d29af36e5 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-01-23 18:00:53 -03:00
Federico Maccaroni
4472d7f9a8 PM-3349 PM-3350 Fix external link icon 2024-01-23 17:59:32 -03:00
Federico Maccaroni
999579915c Merge branch 'main' into feature/maui-migration 2024-01-23 17:34:55 -03:00
Federico Maccaroni
63904fd303 PM-3350 Fix Avatar toolbar icon on extensions to load properly and to take advantage of using directly SkiaSharp to do the native conversion to UIImage. Also improved the toolbar item so that size is set appropriately. 2024-01-23 17:34:27 -03:00
Andreas Coroiu
ce550fee74 [PM-5731] feat: scaffold make credential 2024-01-23 14:29:20 +01:00
Andreas Coroiu
f0841eb8b2 [PM-5731] chore: minor clean up 2024-01-23 10:37:04 +01:00
Andreas Coroiu
b23d58c0b1 [PM-5732] feat: finish authenticator assertion implementation
note: CryptoFunctionService still needs Sign implemenation
2024-01-23 10:28:00 +01:00
Federico Maccaroni
2cb6872e4e Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-01-22 16:57:59 -03:00
Federico Maccaroni
f539bf051d Merge branch 'main' into feature/maui-migration 2024-01-22 16:57:45 -03:00
Federico Maccaroni
14f845d623 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys
# Conflicts:
#	src/App/Platforms/iOS/AppDelegate.cs
2024-01-22 16:57:07 -03:00
Federico Maccaroni
133a80acef PM-3350 Fixed CancellationTokenSource proper disposal 2024-01-22 12:30:29 -03:00
Andreas Coroiu
e8f6c37c06 [PM-5731] feat: implement assertion without signature 2024-01-22 16:08:15 +01:00
Federico Maccaroni
b43790de9a Merge branch 'main' into feature/maui-migration 2024-01-22 10:00:30 -03:00
Andreas Coroiu
d0e0f0ecdb [PM-5731] feat: add support for counter 2024-01-22 13:36:53 +01:00
Federico Maccaroni
0bdd63df06 Merge branch 'main' into feature/maui-migration 2024-01-19 15:14:50 -03:00
Federico Maccaroni
c6544b49e9 PM-3350 Removed TapGesture Window MAUI hack from iOS.Extension and iOS.ShareExtension 2024-01-19 15:14:22 -03:00
Federico Maccaroni
8e1a8b5f0e PM-3349 PM-3350 Updated XCode version on build.yml to 15.1 2024-01-19 15:02:53 -03:00
Federico Maccaroni
4717f5e230 PM-3349 PM-3350 Improved code safety with try...catch, better invoke on main thread and better null handling. 2024-01-19 15:01:31 -03:00
Andreas Coroiu
ad80defa40 [PM-5731] fix: tests a bit, needed some additional "arrange" steps 2024-01-19 16:42:24 +01:00
Andreas Coroiu
0dc281edc1 [PM-5731] feat: check for UV when reprompt is active 2024-01-19 15:47:06 +01:00
Andreas Coroiu
378551e2d5 [PM-5731] feat: add user does not consent test 2024-01-19 15:18:46 +01:00
Andreas Coroiu
dbe4110027 [PM-5731] feat: add tests for successful UV requests 2024-01-19 11:36:34 +01:00
Andreas Coroiu
a08466d220 [PM-5731] feat: find discoverable credentials 2024-01-19 11:23:56 +01:00
Andreas Coroiu
66a01e30d3 [PM-5731] feat: ask for credentials when found 2024-01-19 10:45:03 +01:00
Federico Maccaroni
01ee1ff845 PM-3350 Enabled back UseInterpreter on iOS Release given that it crashes on startup without it. 2024-01-18 18:54:34 -03:00
Federico Maccaroni
75b4655f38 PM-3350 Testing UseInterpreter false on CI build 2024-01-18 16:32:00 -03:00
mpbw2
9b2f596d15 Move Android camera/scan changes to xaml 2024-01-18 11:31:16 -05:00
Andreas Coroiu
cc89b6a5d5 [PM-5731] feat: add rp mismatch test 2024-01-18 10:15:21 +01:00
Andreas Coroiu
32c2f2aac4 [PM-5731] feat: add first test 2024-01-18 09:23:06 +01:00
Andreas Coroiu
f9b4e30b0b [PM-5731] feat: implement get assertion params object 2024-01-18 09:23:05 +01:00
mpbw2
55fb71744d Improve TOTP scan performance on Android 2024-01-17 13:03:11 -05:00
Federico Maccaroni
ee252be634 Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys 2024-01-17 13:22:47 -03:00
Federico Maccaroni
66f0471f2e Merge branch 'main' into feature/maui-migration 2024-01-17 13:22:26 -03:00
Federico Maccaroni
6b9eeba88d PM-3350 PM-3349 Added property to Directory.Build.props to enable Unit Testing globally so Test runners work 2024-01-17 13:22:00 -03:00
Federico Maccaroni
0a1fbfafb5 PM-3349 Fix navigation when coming from autofill with Accessibility Services enabled. The user was getting into Home page instead of where they were, with this workaround the app navigates as if the account has been switched, leaving the user as closely as possible to where they were, basically on the first screen for the current state of the user. 2024-01-17 09:34:31 -03:00
Federico Maccaroni
0a5d772886 PM-3350 Fix build.yml format 2024-01-16 11:32:27 -03:00
Federico Maccaroni
70c8a264d2 PM-3350 PM-3349 Enable running Core tests 2024-01-15 17:35:37 -03:00
Federico Maccaroni
b5fbb2cade Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys
# Conflicts:
#	bitwarden-mobile.sln
2024-01-15 17:24:33 -03:00
Federico Maccaroni
9027755b71 PM-3350 PM-3349 Updated Readme with MAUI and main branch 2024-01-15 17:20:46 -03:00
Federico Maccaroni
6d625f285b PM-3350 PM-3349 Updated Unit Test projects to NET 8.0 and fixed it to work with Core project reference. Also fixed a test that was breaking due to CIpherKey creation being wrong. Added "UT" as a constant to add when building/running Core.Test project so we have something on the context that tells us that is for a UT. With this I had to remove FFImageLoading on UT context because it doesn't support NET 8.0 2024-01-15 17:18:51 -03:00
Federico Maccaroni
822ad7564e Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys
# Conflicts:
#	src/iOS.Autofill/iOS.Autofill.csproj
2024-01-12 13:59:59 -03:00
Federico Maccaroni
1949a450fd PM-3350 Improved code safety adding a lot of try...catch and logging throughout the app. Also made the invoking on main thread safer on several places of the app. Additionally, on the GroupingsPageViewModel changed the code removing the old Xamarin hack and just using Replace directly instead of Clearing first to see if that fixes the crash we're having sometimes on the app. 2024-01-12 13:54:34 -03:00
Federico Maccaroni
27fa79e0bd Merge branch 'main' into feature/maui-migration 2024-01-12 09:16:10 -03:00
Federico Maccaroni
1e29eacc61 PM-3350 Removed "iOS" old folder project that has been moved into the MAUI Single app project. 2024-01-11 17:51:34 -03:00
Federico Maccaroni
b81d26d589 Bump main iOS version 2024-01-11 14:18:34 -03:00
Federico Maccaroni
cd107b6161 Merge branch 'main' into feature/maui-migration
# Conflicts:
#	src/App/Platforms/Android/AndroidManifest.xml
#	src/iOS.Core/Renderers/CollectionView/ExtendedGroupableItemsViewController.cs
2024-01-11 14:17:34 -03:00
Federico Maccaroni
7ac3646fb0 PM-3350 Added nuget.config so we add the nuget package source for MAUI Nightly builds 2024-01-11 10:31:36 -03:00
Federico Maccaroni
d1e4e8645a PM-3350 Updated MauiVersion to 8.0.4-nightly.* to have the TapGestureRecognizer fix applied. This is done on the Directory.Build.props so we don't have to change it on every csproj. Also removed the workaround of TapGestureHack and fix the Show environment picker to work on the extensions as well. 2024-01-10 19:53:53 -03:00
Federico Maccaroni
36a648e53e Merge branch 'feature/maui-migration' into feature/maui-migration-passkeys
# Conflicts:
#	src/Core/Core.csproj
2024-01-09 18:10:56 -03:00
Federico Maccaroni
6c04ac67b1 PM-3349 Fix Picker selection style by doing a custom PickerHandler for Android which uses SetSingleChoiceItems(...) to provide with the appropriate UI 2024-01-09 15:08:45 -03:00
Federico Maccaroni
dfb7a0621f PM-5154 Added Fido2AuthenticationService to provide us a wrapper for the actual implementation 2024-01-03 19:02:49 -03:00
mpbw2
1eb9e5f8ea add cred manager project to build config 2024-01-03 16:36:56 -05:00
mpbw2
b149e7549c initial commit of android credential provider service (wip) 2024-01-03 16:30:44 -05:00
Federico Maccaroni
e3877cc589 PM-5154 Continue Passkeys Autofill in iOS 2024-01-03 18:22:03 -03:00
Federico Maccaroni
275ae76761 PM-5154 Start implementing Passkeys Autofill in iOS 2024-01-03 18:21:02 -03:00
Federico Maccaroni
a1e4f0aaa2 PM-3350 Updated version to 2024.1.0 on iOS 2024-01-02 13:21:32 -03:00
Federico Maccaroni
adaef0d15b Merge branch 'main' into feature/maui-migration
# Conflicts:
#	src/App/Platforms/Android/AndroidManifest.xml
2024-01-02 13:20:15 -03:00
Dinis Vieira
fa4a2247e3 PM-3349 Changed the "track color" for the Android switch so that the color is different from the "thumb" 2023-12-23 23:09:11 +00:00
Dinis Vieira
5d2fc4530f PM-3349 Minor aligment improvemment for region selection in HomePage 2023-12-23 15:25:41 +00:00
Dinis Vieira
9b64af3423 PM-3349 Fix for icon and text spacing in some list items 2023-12-23 14:30:24 +00:00
Dinis Vieira
b6ff6e34f6 PM-3349 Added custom window so that we can always get the current Active Window. This is used to support the Android Autofil and multi-window scenarios. 2023-12-23 12:30:21 +00:00
Dinis Vieira
6d4c706026 Merge branch 'feature/maui-migration-android-autofill' into feature/maui-migration-android-autofill-redirect-page 2023-12-22 22:16:17 +00:00
Dinis Vieira
14fd026ea0 Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill 2023-12-22 22:15:58 +00:00
Dinis Vieira
a4392a8730 PM-3349 PM-3350 Changed search icon used in app to avoid issue with icon size on iOS 2023-12-22 22:15:01 +00:00
Dinis Vieira
1b885ea438 PM-3350 Fix for Delete Account buttons on iOS 2023-12-22 17:07:43 +00:00
mpbw2
fa022a1a4f Revert "initial commit of android credential provider service (wip)"
This reverts commit 6011b63958.
2023-12-20 18:23:08 -05:00
mpbw2
6011b63958 initial commit of android credential provider service (wip) 2023-12-20 18:04:21 -05:00
Dinis Vieira
7d79b98bf2 PM-3349 Minor ui fix (space between buttons in delete account page) 2023-12-20 19:04:26 +00:00
Dinis Vieira
d4e75e9de8 PM-3349 Added Window events unsubscription of events. Also changed code to avoid potentially having multiple autofillwindow 2023-12-18 22:56:21 +00:00
Dinis Vieira
c3370b58ec Added some comments and improvemments. 2023-12-18 00:24:38 +00:00
Dinis Vieira
3de13325c9 Merge branch 'feature/maui-migration-android-autofill' into feature/maui-migration-android-autofill-redirect-page 2023-12-17 22:26:46 +00:00
Dinis Vieira
c253c110c1 Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill 2023-12-17 22:26:33 +00:00
Dinis Vieira
bf35d1f2dc PM-3349 minor fix to add space in HomePage between the region picker labels. 2023-12-17 22:26:15 +00:00
Dinis Vieira
05b6aa90b6 Improved autofill workaround to better handle switching between windows. 2023-12-17 22:05:37 +00:00
Dinis Vieira
e39898bba6 Reusing App.xaml.cs Navigation for the Android RedirectPage
Some other cleanup and changes
2023-12-16 15:18:07 +00:00
Dinis Vieira
da0866cc85 Merge branch 'feature/maui-migration-android-autofill' into feature/maui-migration-android-autofill-redirect-page 2023-12-15 23:51:08 +00:00
Dinis Vieira
b3140381ab Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill
# Conflicts:
#	src/Core/App.xaml.cs
2023-12-15 23:50:48 +00:00
Dinis Vieira
c01a8f8d93 PM-3349 Ensure "_isResumed=true" is set on App.xaml.cs:Bootstrap 2023-12-15 23:48:11 +00:00
Federico Maccaroni
8484b4af30 PM-3349 Uncommented the Deploy step for Android job 2023-12-15 16:38:40 -03:00
Federico Maccaroni
6b9faed45f PM-3349 Commented the Deploy step for Android job given that we're using the hotfix-rc branch for testing iOS on TestFlight 2023-12-15 12:08:01 -03:00
Federico Maccaroni
770a1c5dfe Merge branch 'main' into feature/maui-migration 2023-12-15 11:53:57 -03:00
Dinis Vieira
3c87d4db1c minor improvemments on autofill-redirect 2023-12-15 13:48:34 +00:00
Dinis Vieira
8b3c6ab35f minor change (public to private fields) 2023-12-14 22:43:50 +00:00
Dinis Vieira
90912977c4 Merge branch 'feature/maui-migration-android-autofill' into feature/maui-migration-android-autofill-redirect-page 2023-12-14 22:02:39 +00:00
Dinis Vieira
740b368b8c Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill 2023-12-14 22:00:06 +00:00
Federico Maccaroni
3a40a4cda8 PM-3349 Upgraded Android targetSdkVersion to 34 2023-12-14 17:52:11 -03:00
Federico Maccaroni
9bcd2e51f7 Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-12-14 17:32:48 -03:00
Federico Maccaroni
741214a1cc PM-3349 PM-3350 Enabled argon2id and fixed one issue with the Uris when getting the icon image 2023-12-14 17:32:43 -03:00
Federico Maccaroni
aad87dfdce Update .github/workflows/build.yml setup-xcode commit hash
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2023-12-14 17:28:10 -03:00
Federico Maccaroni
8fc1e9a3b9 PM-3350 Updated Plugin.Fingerprint so biometrics work 2023-12-14 16:32:04 -03:00
Federico Maccaroni
2a8e15146e PM-3350 Apply Cryptography TrimmerRootAssembly only to iOS 2023-12-14 14:28:20 -03:00
Federico Maccaroni
8559d5908e Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-12-14 13:16:41 -03:00
Federico Maccaroni
f60c4d94fe PM-3350 Fix crash on Release by adding Interpreter on iOS and also adding System.Security.Cryptography to be ignored by the linker 2023-12-14 13:16:32 -03:00
Dinis Vieira
05858bea48 Proof of concept for having multiple window in Android for autofill support and navigating with the help of an Extended splash page. 2023-12-13 23:42:58 +00:00
Dinis Vieira
5cbef47fd4 Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill 2023-12-13 22:05:52 +00:00
Dinis Vieira
4bf695d18c PM-3349 Fix for app crashing on Android when Dark mode is enabled
Removed unused button style for android
2023-12-13 19:20:43 +00:00
Dinis Vieira
c24e0dfa28 Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill 2023-12-13 17:30:27 +00:00
Federico Maccaroni
9ccd0834ff Merge branch 'main' into feature/maui-migration 2023-12-13 10:24:29 -03:00
Federico Maccaroni
a806f17d3b PM-3350 Changed linker to use default mode given that "Full" is presenting some problems as the linker is stripping things it shouldn't and we're trying to solve it. So for now we will use the mode "Link SDK assemblies only" so QA can test. 2023-12-12 21:10:51 -03:00
Federico Maccaroni
436a162df2 Merge branch 'main' into feature/maui-migration 2023-12-12 20:19:42 -03:00
Dinis Vieira
f2c298607e Merge branch 'feature/maui-migration' into feature/maui-migration-android-autofill 2023-12-11 22:19:49 +00:00
Federico Maccaroni
b5dbb9ae5e PM-3350 Bumped iOS version 2023-12-11 13:21:31 -03:00
Federico Maccaroni
7a5f7c0274 Merge branch 'master' into feature/maui-migration 2023-12-11 11:42:46 -03:00
Dinis Vieira
17acb57732 PM-3349 Added base structure for avoiding Android Autofill crash. This workaround works but it's not complete as it can't handle the entire workflow when showing CipherSelectionPAge (like checking if it should show LockPage) 2023-12-10 15:05:08 +00:00
Federico Maccaroni
5803635f44 PM-3349 PM-3350 Refactored cipher bindings to have a simpler approach reusing a new CipherItemViewModel to avoid unwanted issues in the app 2023-12-07 23:59:06 -03:00
Federico Maccaroni
19c393842f PM-3350 Cleared left ClipLogger from the iOS extensions debug logging. 2023-12-07 18:14:02 -03:00
Federico Maccaroni
15a306490d Merge branch 'feature/maui-migration' into feature/maui-migration-ios-ext-tap-workaround 2023-12-07 17:41:53 -03:00
Federico Maccaroni
a4a3d31c19 PM-3350 Fix iOS extensions navigation and Window/RootViewController handling for TapGestureRecognizer to work 2023-12-07 17:40:20 -03:00
Dinis Vieira
922dc683af PM-3349 Replaced CrossCurrentActivity plugin with MAUI internal CurrentActivity 2023-12-06 17:27:17 +00:00
Dinis Vieira
bae1b3e891 PM-3349 PM-3350 Minor change: removed unneeded HorizontalTextAlignment from Label. 2023-12-04 16:54:55 +00:00
Federico Maccaroni
a5888827c9 Merge branch 'feature/maui-migration' into feature/maui-migration-ios-ext-tap-workaround 2023-12-04 12:18:01 -03:00
Federico Maccaroni
0348940a12 Merge branch 'master' into feature/maui-migration 2023-12-04 12:17:48 -03:00
Federico Maccaroni
4c2998337d Merge branch 'feature/maui-migration' into feature/maui-migration-ios-ext-tap-workaround 2023-12-04 11:44:03 -03:00
Federico Maccaroni
7ea86380f4 PM-3349 PM-3350 build.yml uncommented jobs so we have a more complete workflow 2023-12-04 11:41:55 -03:00
Federico Maccaroni
406f4425c8 PM-3349 PM-3350 Added MAUI label on self-host settings and on about settings to differentiate from XF app 2023-12-04 11:35:35 -03:00
Federico Maccaroni
95ca911444 PM-3350 Improved MTouch linking and extra args on iOS related csprojs 2023-12-04 11:30:54 -03:00
Federico Maccaroni
fa62510e09 PM-3350 build.yml Added several fixes like not using MtouchUseLLVM on the iOS builds to fix they taking forever to build and some changes on the automation CI to do a debug build for the moment 2023-12-04 10:41:41 -03:00
Federico Maccaroni
65dc73495d PM-3350 build.yml added step to validate app for submitting into App Store and have better logs of it 2023-12-04 10:11:29 -03:00
Federico Maccaroni
02a2e41118 PM-3350 fix build.yml Bitwarden.ipa AppStore exported file 2023-12-04 10:10:18 -03:00
Federico Maccaroni
bd6f8295e7 PM-3350 iOS updated CFBundlerShortVersionString to latest one 2023.10.1 2023-12-04 10:01:56 -03:00
Federico Maccaroni
0a0cb7093b Merge branch 'master' into feature/maui-migration
# Conflicts:
#	.github/workflows/build.yml
2023-12-04 09:59:52 -03:00
Dinis Vieira
465e5eff76 PM-3349 PM-3350 Fix for text getting cut/truncated in both account switcher and ciphers/search lists
Issue is due to MAUI but can be avoided by using slightly different layout
2023-12-03 22:17:16 +00:00
Dinis Vieira
5b756aaf7a PM-3349 PM-3350 Added the ability for users to press "Continue" button as a fallback when using the Yubikey if the "SubmitCommand" doesn't trigger automatically. 2023-12-01 16:49:14 +00:00
Dinis Vieira
d168a7b750 PM-3349 PM-3350 Added workaround for More Options to work on Search and Groupings Page
Updated some code to MAUI Style also
2023-12-01 15:02:12 +00:00
Federico Maccaroni
7f4bbafe3c PM-3350 iOS applied workaround on the iOS Autofill and Share extension to maui embed the navigation page with its content page in the Window 2023-11-30 18:23:54 -03:00
Federico Maccaroni
a5804df6a3 PM-3350 iOS extensions TapGestureRecognizer try Window workaround 2023-11-29 18:42:39 -03:00
Federico Maccaroni
bfa2a51608 PM-3349 build.yml configured FDROID job for MAUI 2023-11-27 17:53:01 -03:00
Federico Maccaroni
32be08daae PM-3349 build.yml enabled android build workflow 2023-11-27 09:24:36 -03:00
Dinis Vieira
0a628cc8a8 PM-3349 PM-3350 Workaround to fix issues with text getting cropped/truncated when a Label has both Multiline and LinebreakMode set 2023-11-26 14:46:30 +00:00
Federico Maccaroni
80c424ed03 Update build.yml disabling iOS job to avoid long running process of publish until we can fix that 2023-11-25 10:17:48 -03:00
Federico Maccaroni
99fb5463cf PM-3350 iOS projs disable linking and set Newstandkit as weak framework 2023-11-24 17:47:12 -03:00
Federico Maccaroni
c5d941e1df PM-3350 added linkskip for iOS csprojs 2023-11-24 15:13:45 -03:00
Federico Maccaroni
3edfef6169 PM-3350 build.yml disable trimming on publish so it's faster 2023-11-24 13:59:09 -03:00
Federico Maccaroni
1c8742511a PM-3350 Added Document.Build.props to disable trimming on publish 2023-11-24 12:50:17 -03:00
Federico Maccaroni
8e424d6c05 PM-3350 build.yml changed image back to be macos-13 to see if the build is faster 2023-11-24 11:17:45 -03:00
Federico Maccaroni
390c303b90 PM-3350 build.yml try to fix ILLINK warnings and changed image to be macos-13-arm64 to see if the build is faster 2023-11-24 10:50:36 -03:00
Federico Maccaroni
443f7282b8 PM-3350 build.yml Upgraded iOS build to run on macos-13 image which has XCode 15, and set the XCode 15 version as currently the default one is 14.x 2023-11-23 18:05:58 -03:00
Federico Maccaroni
50109ee70b PM-3350 build.yml changed nuget restore for dotnet restore on iOS build to fix issue on restoring due to msbuild 2023-11-23 17:38:23 -03:00
Federico Maccaroni
9ffdfd51cc PM-3350 build.yml updated iOS build and ignore Android build to try the CI faster 2023-11-23 17:20:17 -03:00
Federico Maccaroni
04e409f3c6 PM-3349 build.yml add Android "prod" variant 2023-11-23 16:32:10 -03:00
Federico Maccaroni
6c143bad57 PM-3349 build.yml updated env helpers variables and set specific csproj to build on Android so not to build iOS extensions 2023-11-23 15:39:16 -03:00
Federico Maccaroni
286e18059a PM-3349 PM-3350 build.cake updated paths 2023-11-23 14:08:14 -03:00
Federico Maccaroni
553bf9ed0a PM-3349 build.yml commented verify format and just set qa as variant on MAUI Android for faster checks on CI 2023-11-23 13:08:18 -03:00
Federico Maccaroni
ddb27b52d3 PM-3349 build.yml update paths for MAUI Android 2023-11-23 12:49:29 -03:00
Federico Maccaroni
6c504aa710 PM-3349 Started to configure build.yml for MAUI Android 2023-11-23 12:18:31 -03:00
Dinis Vieira
62254aef8d PM-3349 PM-3350 Fixed issue where creating an account with weak/exposed password would get stuck after the Captcha (if a captcha is shown)
Changed App.xaml.cs NavigateImpl to be private
2023-11-21 22:39:44 +00:00
Dinis Vieira
06a0195a6d PM-3349 Added workaround for Android to avoid issues with setting MainPage when app is in background. They are now kept on a Queue to be executed after the app has resumed.
Updated some things on App.xaml.cs to the new MAUI style
2023-11-21 21:26:14 +00:00
Dinis Vieira
df2b0b21d5 PM-3350 Added workaround for iOS Avatar icon again. 2023-11-20 22:07:40 +00:00
Federico Maccaroni
e6b1bab860 PM-3350 Updated AppCenter package to latest version 5.0.3 and updated some things into MAUI style 2023-11-20 17:29:16 -03:00
Federico Maccaroni
ce41eb0578 PM-3350 Fixed account toolbar item and TitleView on SendAddOnlyPage, also removed comments on AvatarImageSource given the workaround is not needed anymore to draw the image successfully. 2023-11-20 16:09:23 -03:00
Federico Maccaroni
1a0b52d644 PM-3350 Fixed/Updated all MAUI-Migration TODOs 2023-11-20 13:10:03 -03:00
Federico Maccaroni
16ada4993c Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration
# Conflicts:
#	src/Core/Pages/Settings/OtherSettingsPageViewModel.cs
2023-11-20 10:49:42 -03:00
Federico Maccaroni
3795f3aa17 PM-3350 Added watchOS app to main project and fixed some csproj conditions for runtime identifiers on iOS. 2023-11-20 10:44:56 -03:00
Dinis Vieira
eceb506c77 Merge branch 'master' into feature/maui-migration
Fixed conflicts

# Conflicts:
#	src/App/Resources/AppResources.cs.Designer.cs
#	src/App/Resources/AppResources.da.Designer.cs
#	src/App/Resources/AppResources.de.Designer.cs
#	src/App/Resources/AppResources.es.Designer.cs
#	src/App/Resources/AppResources.fi.Designer.cs
#	src/App/Resources/AppResources.fr.Designer.cs
#	src/App/Resources/AppResources.hi.Designer.cs
#	src/App/Resources/AppResources.hr.Designer.cs
#	src/App/Resources/AppResources.hu.Designer.cs
#	src/App/Resources/AppResources.id.Designer.cs
#	src/App/Resources/AppResources.it.Designer.cs
#	src/App/Resources/AppResources.ja.Designer.cs
#	src/App/Resources/AppResources.nl.Designer.cs
#	src/App/Resources/AppResources.pl.Designer.cs
#	src/App/Resources/AppResources.pt-BR.Designer.cs
#	src/App/Resources/AppResources.pt-PT.Designer.cs
#	src/App/Resources/AppResources.ro.Designer.cs
#	src/App/Resources/AppResources.ru.Designer.cs
#	src/App/Resources/AppResources.sk.Designer.cs
#	src/App/Resources/AppResources.sv.Designer.cs
#	src/App/Resources/AppResources.th.Designer.cs
#	src/App/Resources/AppResources.tr.Designer.cs
#	src/App/Resources/AppResources.uk.Designer.cs
#	src/App/Resources/AppResources.vi.Designer.cs
#	src/App/Resources/AppResources.zh-Hans.Designer.cs
#	src/App/Resources/AppResources.zh-Hant.Designer.cs
#	src/Core/Controls/Settings/BaseSettingControlView.cs
#	src/Core/Pages/Accounts/EnvironmentPageViewModel.cs
#	src/Core/Pages/Accounts/HomePage.xaml.cs
#	src/Core/Pages/Accounts/HomePageViewModel.cs
#	src/Core/Pages/Accounts/SetPasswordPageViewModel.cs
#	src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs
#	src/Core/Pages/TabsPage.cs
#	src/Core/Services/StateMigrationService.cs
#	src/Core/Utilities/BoolToColorConverter.cs
2023-11-19 15:06:02 +00:00
Dinis Vieira
2c7870d660 PM-3349 PM-3350 Removed AsyncCommand "wrapper" and added AsyncRelayCommand directly in all ViewModels that were using the other one. 2023-11-16 22:31:01 +00:00
Dinis Vieira
f02b3415a3 PM-3350 Removed workaround for iOS issue with Avatar icon as it's now fixed in latest .Net8 release. 2023-11-14 23:24:10 +00:00
Federico Maccaroni
beda4e9ff8 PM-3350 Updated PCL Crypto to latest alpha version to fix "Dll not found NCrypt" issue 2023-11-14 18:43:23 -03:00
Federico Maccaroni
df4d89cd52 PM-3350 Fixed iOS Extensions navigation to several pages and improved avoiding duplicate calls to OnNavigatedTo 2023-11-14 13:43:59 -03:00
Federico Maccaroni
5f12bb9747 PM-3350 Fixed iOS Share extension lazy views loading and an issue with the avatar loading. Also discovered issue with TapGestureRecognizer not working on MAUI Embedding 2023-11-13 21:30:56 -03:00
Federico Maccaroni
5712639492 Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-11-13 14:44:24 -03:00
Federico Maccaroni
e0a3c301fb PM-3349 PM-3350 Changed SendViewCell and its binding to be directly against the ViewModel 2023-11-13 14:44:19 -03:00
Dinis Vieira
27306fe353 PM-3349 PM-3350 Added the partial MAUI Community Toolkit implementation for TouchEffect. This is a temporary solution until they finalize this and add it to their nuget package.
This allows implementing the LongPressCommand in AccountSwitchingOverlay and also have the "Ripple effect" animation when touching an item in Android
2023-11-13 09:59:54 +00:00
Dinis Vieira
a31f15559f PM-3350 Fix for colored html text on iOS 2023-11-11 15:05:51 +00:00
Dinis Vieira
0e75f3f5c8 PM-3349 Fix for HTML Label on Android. Color hex doesn't need to be cropped anymore. 2023-11-10 23:30:54 +00:00
Federico Maccaroni
363da063fa PM-3349 PM-3350 Changed AccountViewCell and its binding to be directly against the ViewModel 2023-11-10 17:30:08 -03:00
Federico Maccaroni
974a571455 PM-3349 PM-3350 Changed binding set for CipherViewCell so it updates accordingly 2023-11-10 14:41:41 -03:00
Dinis Vieira
e0c721098c PM-3349 PM-3350 Migrated remaining AutomationProperties to SemanticProperties.
All 'IsInAccessibleTree="True"' were deleted.
'IsInAccessibleTree="False"' were kept and stayed in code.
2023-11-09 23:01:04 +00:00
Federico Maccaroni
a86f6e3034 PM-3350 Added configurations for Release mode (no FDroid yet) 2023-11-09 17:15:02 -03:00
Federico Maccaroni
fe17288b99 Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-11-09 16:16:26 -03:00
Federico Maccaroni
7324da9d47 PM-3350 Fixed iOS extensions (iOS.Extension and iOS.ShareExtension) to load and commented argon2id from debug configuration until we have the .a compiled again with the new platform/arch 2023-11-09 16:16:17 -03:00
Dinis Vieira
69aa6fc044 PM-3349 PM-3350 Started using OnNavigatedTo/From instead of On(Dis)Appearing for LoginPage and LoginSSOPage to avoid the "Modal loading" issues in iOS
Also had to add IsInitialized logic to these pages because OnNavigatedTo can be called twice in some scenario.
Some minor migrations of Device to DeviceInfo was also done
2023-11-09 15:09:37 +00:00
Dinis Vieira
e840dc2e30 Merge branch 'master' into feature/maui-migration
Fixed conflicts and added null check on ForwardEmailDomainName

# Conflicts:
#	src/Core/Pages/Vault/CipherAddEditPage.xaml
#	src/Core/Pages/Vault/CipherDetailsPage.xaml
#	src/iOS.Core/Renderers/CollectionView/ExtendedGroupableItemsViewController.cs
2023-11-05 23:59:30 +00:00
Dinis Vieira
eb25ee5d1b PM-3349 Fix for Android buttons having all letters in Caps 2023-11-04 18:39:08 +00:00
Dinis Vieira
840f24dbe5 PM-3349 Fix for TabGestureRecognizer not working inside the StackLayout area of IconLabelButton 2023-11-04 18:08:44 +00:00
Dinis Vieira
c6309173ba PM-3349 PM-3350 Migrated IconLabelButton Frames to Borders to fix issue with TapGestureRecognizer in Android
Also fixed some minor "styles" for normal Button and IconLabelButton (both Android and iOS)
2023-11-04 17:48:59 +00:00
Dinis Vieira
946c465f0c PM-3349 Added Handler that enables the ExtendedDatePicker to get IsFocused events in Android. This is a workaround for fixing the current bug where it's not possible to select the "current day" in the expiration date of a Send.
Fix for TimePicker not displaying default Time Value
Updated some "Device" code to the new MAUI "DeviceInfo"
2023-11-02 02:32:12 +00:00
Dinis Vieira
e90409d842 PM-3350 Added Scrollview on HomePage so that the "Create account" button can be accessed in smaller devices like iPhone SE. 2023-10-29 21:34:33 +00:00
Dinis Vieira
484b5a5160 PM-3349 PM-3350 Minor change to HomePage to set fixed Image height otherwise it takes more space than it did in the old Xamarin Forms app.
Added HIdeSoftInputOnTapped on several pages (the ones with Entry controls) to allow hiding the keyboard when tapping "outside" of it. (just like we did in Xamarin Forms app)
2023-10-28 19:02:54 +01:00
Dinis Vieira
2688209752 PM-3350 Removed duplicate reference to LaunchScreen.storyboard 2023-10-27 17:19:20 +01:00
Dinis Vieira
53e0e55915 PM-3350 Quick workaround to allow 2nd factor auth to not get stuck in iOS in modals.
Updated some older "Device" code to the newer MAUI code.
2023-10-27 15:36:47 +01:00
Dinis Vieira
ca57948d9f PM-3350 Removed MAUI Splash Screen. Fixed iOS Privacy Screen logo (hardcoded image to avoid it getting cropped) 2023-10-27 11:41:21 +01:00
Dinis Vieira
aaf082faba PM-3349 Fixed white tint color not appearing on images added as MAU IImage SVG
PM-3349 PM-3350 Fix for Avatar text not adjusting to white/black color correctly
2023-10-25 22:20:01 +01:00
Dinis Vieira
e7aeb08cae PM-3350 Checked some [MAUI-Migration] and changed as needed.
TimePickerHandlerMappings: Remove old code for forcing the Wheel. After testing without it wheel picker is still used so this code shouldn't be needed anymore.
AppDelegate.ContinueUserActivity: Uncommented and changed the iOS ContinueUserActivity. It needs to call Platform.ContinueUserActivity according with Xamarin Essentials migration docs.
2023-10-24 23:37:18 +01:00
Dinis Vieira
f177968958 Checked some [MAUI-Migration] and deleted when it's working as intended.
SearchBarHandlerMapping: IME options working as intended
SliderHandlerMappings: The MAUI "replacement" for Color.Default seems to be White so the old use case doesn't seem to be needed anymore.
2023-10-24 22:25:05 +01:00
Dinis Vieira
f1d59210f9 PM-3349 PRM-3350 Replaced XZing with Camera.MAUI for QRCodes 2023-10-23 15:17:14 +01:00
Dinis Vieira
62213c0aaf PM-3350 Added some missing images in iOS 2023-10-20 13:33:39 +01:00
Dinis Vieira
8be8abb8fe PM-3350 Migrated some Device to DeviceInfo and added temporary workaround with some comments to be able to see the Generated Password on iOS 2023-10-20 00:04:19 +01:00
Dinis Vieira
174acbc558 PM-3349
Added Argon libraries for Android
minor change to gitignore so that the Argon x86 lib is not ignored on the Android platform
2023-10-18 15:41:20 +01:00
Dinis Vieira
4bcc7c0d71 Enabled argon2Id for iOS 2023-10-18 12:20:41 +01:00
Dinis Vieira
14b2960f30 PM-3350 Removed ButtonHandlerMappings and some other code related with fonts as MAUI is taking care of Accessibility and no custom code should be needed
Migrated SelectableLabelRenderer to Handler
Cleaned LabelHandlerMappings and added logic to migrate the CustomLabelRenderer
2023-10-18 01:21:19 +01:00
Dinis Vieira
455c3a257c PM-3350 Migrated the CustomViewCellRenderer for iOS 2023-10-17 22:23:04 +01:00
Dinis Vieira
8c623a2067 PM-3349 ToolbarHandler created for setting text on Android go back buttons. 2023-10-16 20:45:58 +01:00
Dinis Vieira
3cdf1c2f0e PM-3349 Replaced the FabShadowEffect with the new MAUI Shadow to fix the buggy shadows on the Android Fab Button. 2023-10-16 00:47:52 +01:00
Dinis Vieira
ce9503fa0c PM-3349 PM-3350
Added (migrated) CustomNavigationHandler (which should partially fix the AvatarIcon in the NavBar in iOS)
Added (migrated) CustomContentPageHandler (which should mostly place the AvatarIcon in the navBar in the correct place for iOS)
Added Task.Delay (workaround) to allow the Avatar to load in iOS on the LoginPage
Added workaround for iOS bug with the toolbar size (more info in comment in AvatarImageSource.cs)
Went through the AccountViewCell MAUI-Migration comments. (and deleted/added more comments as needed)
Migrated some Device calls to DeviceInfo and MainThread
Added (migrated) CustomTabbedHandler (for managing the iOS TabBar)
2023-10-15 22:06:26 +01:00
Dinis Vieira
2e4da1b87d hardcoded AccountViewCell Avatar image to 40x40 to avoid current iOS/Android bugs where they fill much larger space. 2023-10-13 18:49:30 +01:00
Dinis Vieira
d63a219272 PM-3349 Implemented HybridWebViewHandler for iOS 2023-10-07 23:44:24 +01:00
Dinis Vieira
c92cd90a97 PM-3349 Implemented HybridWebViewHandler for Android which enables 2nd factor auth flows
Ensured CustomTabbedPageHandler had it's DisconnectHandler called
Some minor code upgrades of older obsolete Xamarin Forms code.
2023-10-07 17:25:29 +01:00
Dinis Vieira
1dcd3a3daa PM-3349 Changed UseMauiApp init so that Android Handlers still get added 2023-10-07 16:56:08 +01:00
Federico Maccaroni
efb8763d3c Merge branch 'feature/maui-migration' of https://github.com/bitwarden/mobile into feature/maui-migration 2023-10-04 12:45:08 -03:00
Federico Maccaroni
90649d1c8b PM-3350 MAUI Migration Fix iOS Autofill extension 2023-10-04 12:35:43 -03:00
Dinis Vieira
828055791f PM-3349 Removed Deploy from iOS.Autofill to allow running Android
Changed MainApplication SpecialFolder.Personal to SpecialFolder.LocalApplicationData
2023-10-02 21:15:33 +01:00
Dinis Vieira
87eebda55f Changes to solution to hopefully fix Config Mappings 2023-10-02 16:40:31 +01:00
Dinis Vieira
7542d1ae1c fix conflicts 2023-10-02 16:08:09 +01:00
Federico Maccaroni
990de4ea4e PM-3349 PM-3350 MAUI Migration Start iOS extensions 2023-10-02 12:05:57 -03:00
Dinis Vieira
0dbc23f734 PM-3349 PM-3350 Add null checks on CipherDetailsPageVM to avoid crash opening Secure Notes. 2023-10-01 23:15:55 +01:00
Dinis Vieira
9f6c8601d3 TabBarEffect removed and it's behavior is now taken care of by CustomTabbedPageHandler 2023-10-01 22:32:37 +01:00
Dinis Vieira
8b7f9b9fb3 PM-3349: Android
Added CustomTabbedPageHandler for Android to handle the tab "reselection" for PopToRoot.
Commented support for Windows in App.csproj
Disabled Interpreter on Android to avoid very slow app in Debug (during Login for example)
Added some null checks that were causing crashes (on GeneratorPageVM and PickerVM)
Minor TabsPage cleanup
2023-10-01 15:35:04 +01:00
Federico Maccaroni
d17789d5ee PM-3349 PM-3350 MAUI Migration fix nullable bindings and fallbacks 2023-09-29 12:27:12 -03:00
Federico Maccaroni
b8f0747dd4 PM-3349 PM-3350 MAUI Migration fix nullable exception bindings and AsyncCommand canExecute null exception 2023-09-29 12:12:01 -03:00
Federico Maccaroni
8ef9443b1e PM-3349 PM-3350 MAUI Migration Initial 2023-09-29 11:02:19 -03:00
359 changed files with 4447 additions and 19393 deletions

31
.github/CODEOWNERS vendored
View File

@@ -7,11 +7,21 @@
# Default file owners
* @bitwarden/dept-development-mobile
# DevOps for Actions and other workflow changes
.github/workflows @bitwarden/dept-devops
# DevOps for Version Bumping
src/App/Platforms/Android/AndroidManifest.xml
src/iOS.Autofill/Info.plist
src/iOS.Extension/Info.plist
src/iOS.ShareExtension/Info.plist
src/App/Platforms/iOS/Info.plist
## Auth team files ##
## Platform team files ##
appIcons @bitwarden/team-platform-dev
build.cake @bitwarden/team-platform-dev
## Vault team files ##
src/watchOS @bitwarden/team-vault-dev
@@ -20,32 +30,17 @@ src/watchOS @bitwarden/team-vault-dev
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
## Crowdin Sync files ##
src/Core/Resources/Localization @bitwarden/team-tools-dev
src/App/Resources @bitwarden/team-tools-dev
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev
store/apple @bitwarden/team-tools-dev
store/google @bitwarden/team-tools-dev
## Locales ##
src/Core/Resources/Localization/AppResources.Designer.cs
src/Core/Resources/Localization/AppResources.resx
src/App/Resources/AppResources.Designer.cs
src/App/Resources/AppResources.resx
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
store/apple/en
store/google/en
## Utils ##
store/google/Publisher
## These workflows have joint ownership ##
.github/workflows/build.yml @bitwarden/dept-bre @bitwarden/dept-development-mobile
.github/workflows/build-beta.yml @bitwarden/dept-bre @bitwarden/dept-development-mobile
.github/workflows/cleanup-rc-branch.yml @bitwarden/dept-bre @bitwarden/dept-development-mobile
.github/workflows/release.yml @bitwarden/dept-bre @bitwarden/dept-development-mobile
.github/workflows/version-auto-bump.yml @bitwarden/dept-bre @bitwarden/dept-development-mobile
.github/workflows/version-bump.yml @bitwarden/dept-bre @bitwarden/dept-development-mobile
# Shared ownership for version bump automation
src/App/Platforms/Android/AndroidManifest.xml
src/iOS.Autofill/Info.plist
src/iOS.Extension/Info.plist
src/iOS.ShareExtension/Info.plist
src/App/Platforms/iOS/Info.plist

View File

@@ -6,20 +6,8 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
> [!WARNING]
> Testing the new Bitwarden Beta apps? Submit your report in [bitwarden/android](https://github.com/bitwarden/android) or [bitwarden/ios](https://github.com/bitwarden/ios)
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: checkboxes
id: production
attributes:
label: Production Build
options:
- label: I'm using the legacy Bitwarden app pubicly available in App Store / Play Store and I'm aware that Bitwarden Beta bugs should be reported in [bitwarden/android](https://github.com/bitwarden/android) or [bitwarden/ios](https://github.com/bitwarden/ios)
validations:
required: true
- type: textarea
id: reproduce
attributes:
@@ -85,3 +73,9 @@ body:
description: What version of our software are you running? (go to "Settings" → "About" in the app)
validations:
required: true
- type: checkboxes
id: beta
attributes:
label: Beta
options:
- label: Using a pre-release version of the application.

View File

@@ -1,11 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Native Android Beta Bug Reports
url: https://github.com/bitwarden/android/issues
about: Bugs found in the new native Android Beta app should be reported in [bitwarden/android](https://github.com/bitwarden/android)
- name: Native iOS BETA Bug Reports
url: https://github.com/bitwarden/ios/issues
about: Bugs found in the new native iOS Beta app should be reported in [bitwarden/ios](https://github.com/bitwarden/ios)
- name: Customer Support
url: https://bitwarden.com/contact/
about: Please contact our customer support for account issues and general customer support.

35
.github/labeler.yml vendored
View File

@@ -1,26 +1,19 @@
android:
- changed-files:
- any-glob-to-any-file:
- src/App/*
- src/Core/*
- src/Android/*
- 'src/Xamarin.AndroidX.Credentials/*'
- src/App/*
- src/Core/*
- src/Android/*
iOS:
- changed-files:
- any-glob-to-any-file:
- src/App/*
- src/Core/*
- lib/ios/*
- src/iOS/*
- 'src/iOS.Autofill/*'
- 'src/iOS.Core/*'
- 'src/iOS.Extension/*'
- 'src/iOS.ShareExtension/*'
- 'src/iOS.Widget/*'
- src/watchOS/*
- src/App/*
- src/Core/*
- lib/ios/*
- src/iOS/*
- 'src/iOS.Autofill/*'
- 'src/iOS.Core/*'
- 'src/iOS.Extension/*'
- 'src/iOS.ShareExtension/*'
- 'src/iOS.Widget/*'
- src/watchOS/*
watchOS:
- changed-files:
- any-glob-to-any-file:
- src/watchOS/*
- src/watchOS/*

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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,3 @@
<EFBFBD>
 K<>Y#<23>(<28><><EFBFBD><EFBFBD>EI֐߄T?)l<><6C><EFBFBD><18><><10>"=<3D>|<7C>'e<><0E>m<EFBFBD>/~<7E><>' F<><46>><3E><><EFBFBD><EFBFBD>l<EFBFBD>b<EFBFBD>[<5B>+R<><52>iL<69><4C>"<22><><EFBFBD>~V:<3A><>p<EFBFBD>a<17>ڵel%8t<38><74><EFBFBD>y<<3C>n<EFBFBD><6E><EFBFBD>aU<61>w<16>JD<4A><44><1F><>We<57>9<EFBFBD><39><EFBFBD><EFBFBD><x8d<38>O<EFBFBD>j\<14>ד<EFBFBD><D793><EFBFBD>Vq<56><71>֋
Ǻ<EFBFBD>-<2D>#<23><><11><>]$<24>(<28>l,<2C>Br<42><02><>d<><64><EFBFBD>•a-<2D><><EFBFBD>:<3A><>:<3A><04>9b,!Em<02><19><>Qf<>D<EFBFBD>g<EFBFBD><06><0E>x(P<>ȡ~<7E>͹<EFBFBD><CDB9> <09><>[<06><>!:<3A>;f<><66>

Binary file not shown.

BIN
.github/secrets/play_creds.json.gpg vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +1,4 @@
---
name: Automatic responses
on:
issues:
@@ -6,7 +7,7 @@ on:
jobs:
close-issue:
name: 'Close issue with automatic response'
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
permissions:
issues: write
steps:

View File

@@ -1,349 +0,0 @@
name: Build Beta
on:
workflow_dispatch:
inputs:
ref:
description: 'Branch or tag to build'
required: true
default: 'main'
type: string
env:
main_app_folder_path: src/App
main_app_project_path: src/App/App.csproj
target-net-version: net8.0
jobs:
setup:
name: Setup
runs-on: ubuntu-22.04
outputs:
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: 'true'
- name: Check if special branches exist
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
else
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
fi
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
else
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi
ios:
name: Apple iOS
runs-on: macos-14
needs: setup
env:
ios_folder_path: src/App/Platforms/iOS
app_output_name: App
app_ci_output_filename: App_x64_Debug
steps:
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: 15.1
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
with:
dotnet-version: '8.0.x'
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
- name: Print environment
run: |
nuget help | grep Version
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
ref: ${{ inputs.ref }}
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "appcenter-ios-token"
- name: Download Provisioning Profiles secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: profiles
run: |
mkdir -p $HOME/secrets
profiles=(
"dist_beta_autofill.mobileprovision"
"dist_beta_bitwarden.mobileprovision"
"dist_beta_extension.mobileprovision"
"dist_beta_share_extension.mobileprovision"
"dist_beta_bitwarden_watch_app.mobileprovision"
"dist_beta_bitwarden_watch_app_extension.mobileprovision"
)
for FILE in "${profiles[@]}"
do
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
done
- name: Download Google Services secret
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: GoogleService-Info.plist
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
- name: Increment version
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/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.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
- name: Update Entitlements
run: |
echo "##### Updating Entitlements"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>beta<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
- name: Get certificates
run: |
mkdir -p $HOME/certificates
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/ios-distribution |
jq -r .value | base64 -d > $HOME/certificates/ios-distribution.p12
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Set up provisioning profiles
run: |
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_beta_autofill.mobileprovision
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.mobileprovision"
BITWARDEN_UUID=$(grep UUID -A1 -a $BITWARDEN_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $BITWARDEN_PROFILE_PATH "$PROFILES_DIR_PATH/$BITWARDEN_UUID.mobileprovision"
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
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"
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"
- name: Restore packages
run: |
dotnet restore
dotnet tool restore
- name: Setup iOS build CAKE (Testing)
run: dotnet cake build.cake --target iOS --variant beta
- name: Bulid WatchApp
run: |
echo "##### Build WatchApp with Release Configuration"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "##### Done"
- name: Archive Build for App Store
shell: pwsh
run: |
Write-Output "##### Archive for Release ios-arm64"
dotnet publish ${{ env.main_app_project_path }} -c Release -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=ios-arm64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "##### Done"
- name: Archive Build for Mobile Automation
shell: pwsh
run: |
Write-Output "##### Archive Debug for iossimulator-x64"
dotnet build ${{ env.main_app_project_path }} -c Debug -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=iossimulator-x64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "##### Done"
ls ~/Library/Developer/Xcode/Archives
- name: Export .ipa for App Store
env:
EXPORT_OPTIONS_PATH: ./.github/resources/export-options-app-store.plist
EXPORT_PATH: ./bitwarden-export
run: |
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
- name: Export .app for Automation CI
env:
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
EXPORT_PATH: ./bitwarden-export
run: |
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
- name: Show Bitwarden Export
shell: bash
run: ls -a -R ./bitwarden-export
- name: Copy all dSYMs files to upload
env:
EXPORT_PATH: ./bitwarden-export
WATCH_ARCHIVE_DSYMS_PATH: ./src/watchOS/bitwarden.xcarchive/dSYMs/
WATCH_DSYMS_EXPORT_PATH: ./bitwarden-export/Watch_dSYMs
run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/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
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: Bitwarden iOS
path: |
./bitwarden-export/Bitwarden*.ipa
./bitwarden-export/dSYMs/*.*
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
if-no-files-found: error
- name: Install AppCenter CLI
run: npm install -g appcenter-cli
- name: Upload dSYMs to App Center
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
- name: Upload Watch dSYMs to Firebase Crashlytics
run: |
echo "##### Uploading Watch dSYMs to Firebase"
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" \;
- name: Validate app in App Store
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden Beta.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
- name: Deploy to App Store
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden Beta.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-22.04
needs:
- setup
- ios
steps:
- name: Check if any job failed
if: |
(github.ref == 'refs/heads/main'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure')
run: exit 1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
if: failure()
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
if: failure()
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

View File

@@ -1,3 +1,4 @@
---
name: Build
on:
@@ -13,8 +14,6 @@ env:
main_app_folder_path: src/App
main_app_project_path: src/App/App.csproj
target-net-version: net8.0
dotnet-version: '8.0.402'
maui-workload-version: '8.0.402'
jobs:
cloc:
@@ -22,7 +21,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up CLOC
run: |
@@ -32,7 +31,6 @@ jobs:
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
setup:
name: Setup
runs-on: ubuntu-22.04
@@ -41,7 +39,7 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: 'true'
@@ -60,7 +58,6 @@ jobs:
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi
android:
name: Android
runs-on: windows-2022
@@ -70,30 +67,25 @@ jobs:
matrix:
variant: ["prod", "qa"]
env:
android_folder_path: src\App\Platforms\Android
android_folder_path_bash: src/App/Platforms/Android
android_folder_path: src/App/Platforms/Android
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: ${{ env.dotnet-version }}
- name: Install MAUI Workload
run: |
dotnet workload install maui --version ${{ env.maui-workload-version }}
dotnet-version: '8.0.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
- name: Setup Windows builder
run: choco install checksum --no-progress
@@ -101,8 +93,7 @@ jobs:
- name: Install Microsoft OpenJDK 11
run: |
choco install microsoft-openjdk11 --no-progress
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | `
Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Output "Java Home: $env:JAVA_HOME"
- name: Print environment
@@ -113,43 +104,44 @@ jobs:
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
fetch-depth: 0
- name: Download secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p $HOME/secrets
mkdir -p ~/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name app_play-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_play-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name app_upload-keystore.jks --file ./${{ env.android_folder_path_bash }}/app_upload-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name play_creds.json --file $HOME/secrets/play_creds.json --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash
- name: Download secrets - Google Services
- name: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }}
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name google-services.json --file ./${{ env.android_folder_path_bash }}/google-services.json --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.android_folder_path }}/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((11000 + $GITHUB_RUN_NUMBER))
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_folder_path_bash }}/AndroidManifest.xml
./${{ env.android_folder_path }}/AndroidManifest.xml
shell: bash
- name: Restore packages
@@ -158,75 +150,83 @@ jobs:
- name: Restore tools
run: dotnet tool restore
# - name: Run Core tests
# run: |
# dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" `
# /p:CustomConstants=UT
# - name: Verify Format
# run: dotnet tool run dotnet-format --check
# - name: Report test results
# uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0
# if: always()
# with:
# name: Test Results
# path: "**/test-results.trx"
# reporter: dotnet-trx
# fail-on-error: true
- name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" /p:CustomConstants=UT
- name: Report test results
uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0
if: always()
with:
name: Test Results
path: "**/test-results.trx"
reporter: dotnet-trx
fail-on-error: true
- 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: Setup Android build (${{ matrix.variant }})
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build & Sign Android
- name: Build Android
run: |
$configuration = "Release";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
dotnet build $projToBuild -c $configuration -f ${{ env.target-net-version }}-android
- name: Sign Android Build
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: |
$projToBuild = "$($env:GITHUB_WORKSPACE)/${{ env.main_app_project_path }}";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
$signingUploadKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_upload-keystore.jks"
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidPackageFormats=aab `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingUploadKeyStore `
/p:AndroidSigningKeyAlias=upload `
/p:AndroidSigningKeyPass="$($env:UPLOAD_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:UPLOAD_KEYSTORE_PASSWORD)" --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidPackageFormats=aab /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_upload-keystore.jks") /p:AndroidSigningKeyAlias=upload /p:AndroidSigningKeyPass="$($env:UPLOAD_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:UPLOAD_KEYSTORE_PASSWORD)" --no-restore
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.aab";
$signedAabDestPath = "$($env:GITHUB_WORKSPACE)\$($packageName).aab";
$signedAabPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
$signingPlayKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_play-keystore.jks"
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingPlayKeyStore `
/p:AndroidSigningKeyAlias=bitwarden `
/p:AndroidSigningKeyPass="$($env:PLAY_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:PLAY_KEYSTORE_PASSWORD)" --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_play-keystore.jks") /p:AndroidSigningKeyAlias=bitwarden /p:AndroidSigningKeyPass="$($env:PLAY_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:PLAY_KEYSTORE_PASSWORD)" --no-restore
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.apk";
$signedApkDestPath = "$($env:GITHUB_WORKSPACE)\$($packageName).apk";
Copy-Item $signedApkPath $signedApkDestPath
- name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
@@ -234,7 +234,7 @@ jobs:
- name: Upload Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
@@ -242,7 +242,7 @@ jobs:
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
@@ -262,7 +262,7 @@ jobs:
- name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt
@@ -270,7 +270,7 @@ jobs:
- name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
@@ -283,43 +283,39 @@ jobs:
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' ) }}
run: |
$publisherPath = "$($env:GITHUB_WORKSPACE)\store\google\Publisher\bin\Release\net8.0\Publisher.dll"
$credsPath = "$($HOME)\secrets\play_creds.json"
$aabPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden.aab"
$track = "internal"
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/net7.0/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
TRACK="internal"
dotnet $publisherPath $credsPath $aabPath $track
dotnet $PUBLISHER_PATH $CREDS_PATH $AAB_PATH $TRACK
shell: bash
f-droid:
name: F-Droid Build
runs-on: windows-2022
env:
android_folder_path: src\App\Platforms\Android
android_folder_path_bash: src/App/Platforms/Android
android_folder_path: src/App/Platforms/Android
android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: ${{ env.dotnet-version }}
- name: Install MAUI Workload
run: |
dotnet workload install maui --version ${{ env.maui-workload-version }}
dotnet-version: '8.0.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
- name: Setup Windows builder
run: choco install checksum --no-progress
@@ -338,25 +334,26 @@ jobs:
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Download secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: app_fdroid-keystore.jks
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file ${{ env.android_folder_path_bash }}/$FILE --output none
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./${{ env.main_app_folder_path }}/app_fdroid-keystore.jks ./.github/secrets/app_fdroid-keystore.jks.gpg
shell: bash
- name: Increment version
run: |
BUILD_NUMBER=$((11000 + $GITHUB_RUN_NUMBER))
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_manifest_path }}
@@ -364,16 +361,21 @@ jobs:
- name: Clean for F-Droid
run: |
$directoryBuildProps = $($env:GITHUB_WORKSPACE + "/Directory.Build.props");
$appPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
Write-Output "##### Back up project files"
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $directoryBuildProps $($directoryBuildProps + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
@@ -383,39 +385,44 @@ jobs:
$xml.Save($androidManifest);
Write-Output "##### Enabling FDROID constant"
(Get-Content $directoryBuildProps).Replace('<!-- <CustomConstants>FDROID</CustomConstants> -->', '<CustomConstants>FDROID</CustomConstants>') | Set-Content $directoryBuildProps
- name: Restore packages
run: dotnet restore
- name: Build & Sign F-Droid
- name: Build for F-Droid
run: |
$configuration = "Release";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
Write-Output "########################################"
Write-Output "##### Build $configuration FDROID
Write-Output "########################################"
dotnet build $projToBuild -c $configuration -f ${{ env.target-net-version }}-android /p:CustomConstants="FDROID"
- name: Sign for F-Droid
env:
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
run: |
$projToBuild = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_project_path }}";
$projToBuild = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$packageName = "com.x8bit.bitwarden";
Write-Output "########################################"
Write-Output "##### Sign FDroid"
Write-Output "########################################"
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_fdroid-keystore.jks"
dotnet build $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
/p:AndroidSigningKeyAlias=bitwarden `
/p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" ` --no-restore
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=$("app_fdroid-keystore.jks") /p:AndroidSigningKeyAlias=bitwarden /p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" /p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" /p:CustomConstants="FDROID" --no-restore
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\$($packageName)-Signed.apk";
$signedApkDestPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden-fdroid.apk";
$signedApkPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_folder_path }}/bin/Release/${{ env.target-net-version }}-android/publish/$($packageName)-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
@@ -427,44 +434,40 @@ jobs:
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt
if-no-files-found: error
ios:
name: Apple iOS
runs-on: macos-14
runs-on: macos-13
needs: setup
env:
ios_folder_path: src/App/Platforms/iOS
app_output_name: App
app_ci_output_filename: App_x64_Debug
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: 'true'
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: 15.4
xcode-version: 15.1
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: ${{ env.dotnet-version }}
dotnet-version: '8.0.x'
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --version ${{ env.maui-workload-version }}
run: dotnet workload install maui --ignore-failed-sources
- name: Print environment
run: |
@@ -473,6 +476,11 @@ jobs:
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
@@ -485,71 +493,73 @@ jobs:
keyvault: "bitwarden-ci"
secrets: "appcenter-ios-token"
- name: Download Provisioning Profiles secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: profiles
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p $HOME/secrets
profiles=(
"dist_autofill.mobileprovision"
"dist_bitwarden.mobileprovision"
"dist_extension.mobileprovision"
"dist_share_extension.mobileprovision"
"dist_bitwarden_watch_app.mobileprovision"
"dist_bitwarden_watch_app_extension.mobileprovision"
)
mkdir -p ~/secrets
for FILE in "${profiles[@]}"
do
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
done
- name: Download Google Services secret
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: GoogleService-Info.plist
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file src/watchOS/bitwarden/$FILE --output none
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/bitwarden-mobile-key.p12 ./.github/secrets/bitwarden-mobile-key.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/iphone-distribution-cert.p12 ./.github/secrets/iphone-distribution-cert.p12.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_autofill.mobileprovision ./.github/secrets/dist_autofill.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--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
- name: Increment version
run: |
BUILD_NUMBER=$((8000 + $GITHUB_RUN_NUMBER))
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "########################################"
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "########################################"
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/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.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
agvtool new-version -all $BUILD_NUMBER
- name: Update Entitlements
run: |
echo "########################################"
echo "##### Updating Entitlements"
echo "########################################"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
- name: Get certificates
run: |
mkdir -p $HOME/certificates
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/ios-distribution |
jq -r .value | base64 -d > $HOME/certificates/ios-distribution.p12
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \
-T /usr/bin/security
security import ~/secrets/bitwarden-mobile-key.p12 -k build.keychain -P $MOBILE_KEY_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security import ~/secrets/iphone-distribution-cert.p12 -k build.keychain -P $DIST_CERT_PASSWORD \
-T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Set up provisioning profiles
@@ -558,8 +568,8 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_bitwarden_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_bitwarden_watch_app_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
mkdir -p "$PROFILES_DIR_PATH"
@@ -587,50 +597,74 @@ jobs:
- 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 "########################################"
- name: Archive Build for App Store
run: |
echo "##### Archive for Release ios-arm64"
Write-Output "########################################"
Write-Output "##### Archive for Release ios-arm64
Write-Output "########################################"
dotnet publish ${{ env.main_app_project_path }} -c Release -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=ios-arm64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
shell: pwsh
- name: Archive Build for Mobile Automation
run: |
echo "##### Archive Debug for iossimulator-x64"
Write-Output "########################################"
Write-Output "##### Archive Debug for iossimulator-x64
Write-Output "########################################"
dotnet build ${{ env.main_app_project_path }} -c Debug -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=iossimulator-x64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
ls $HOME/Library/Developer/Xcode/Archives
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
shell: pwsh
- name: Export .ipa for App Store
env:
EXPORT_OPTIONS_PATH: ./.github/resources/export-options-app-store.plist
EXPORT_PATH: ./bitwarden-export
run: |
EXPORT_OPTIONS_PATH="./.github/resources/export-options-app-store.plist"
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
EXPORT_PATH="./bitwarden-export"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
- name: Export .app for Automation CI
env:
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
EXPORT_PATH: ./bitwarden-export
run: |
ARCHIVE_PATH="./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64"
EXPORT_PATH="./bitwarden-export"
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
- name: Copy all dSYMs files to upload
env:
EXPORT_PATH: ./bitwarden-export
WATCH_ARCHIVE_DSYMS_PATH: ./src/watchOS/bitwarden.xcarchive/dSYMs/
WATCH_DSYMS_EXPORT_PATH: ./bitwarden-export/Watch_dSYMs
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
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: Bitwarden iOS
path: |
@@ -639,7 +673,7 @@ jobs:
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
@@ -673,30 +707,26 @@ jobs:
|| (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"
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" \;
echo "########################################"
- name: Set up private auth key
run: |
mkdir ~/private_keys
cat << EOF > ~/private_keys/AuthKey_U362LJ87AA.p8
${{ secrets.APP_STORE_CONNECT_AUTH_KEY }}
EOF
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" \;
- name: Validate app in App Store
if: |
(github.ref == 'refs/heads/main'
(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:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool \
--validate-app \
--type ios \
--file "./bitwarden-export/Bitwarden.ipa" \
--apiKey "U362LJ87AA" \
--apiIssuer ${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}
xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
- name: Deploy to App Store
if: |
@@ -705,13 +735,13 @@ jobs:
&& 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:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool \
--upload-app \
--type ios \
--file "./bitwarden-export/Bitwarden.ipa" \
--apiKey "U362LJ87AA" \
--apiIssuer ${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
crowdin-push:
name: Crowdin Push
@@ -725,7 +755,7 @@ jobs:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -740,13 +770,13 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
with:
config: crowdin.yml
crowdin_branch_name: main
crowdin_branch_name: main
upload_sources: true
upload_translations: false
@@ -764,11 +794,27 @@ jobs:
steps:
- name: Check if any job failed
if: |
(github.ref == 'refs/heads/main'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure')
run: exit 1
(github.ref == 'refs/heads/main')
|| (github.ref == 'refs/heads/rc')
|| (github.ref == 'refs/heads/hotfix-rc')
env:
CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }}
F_DROID_STATUS: ${{ needs.f-droid.result }}
IOS_STATUS: ${{ needs.ios.result }}
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$ANDROID_STATUS" = "failure" ]; then
exit 1
elif [ "$F_DROID_STATUS" = "failure" ]; then
exit 1
elif [ "$IOS_STATUS" = "failure" ]; then
exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -785,7 +831,7 @@ jobs:
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -1,52 +0,0 @@
name: Cleanup RC Branch
on:
push:
tags:
- v**
jobs:
delete-rc:
name: Delete RC Branch
runs-on: ubuntu-22.04
steps:
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve bot secrets
id: retrieve-bot-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout main
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: main
token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
- name: Check if a RC branch exists
id: branch-check
run: |
hotfix_rc_branch_check=$(git ls-remote --heads origin hotfix-rc | wc -l)
rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${hotfix_rc_branch_check}" -gt 0 ]]; then
echo "hotfix-rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=hotfix-rc" >> $GITHUB_OUTPUT
elif [[ "${rc_branch_check}" -gt 0 ]]; then
echo "rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=rc" >> $GITHUB_OUTPUT
fi
- name: Delete RC branch
env:
BRANCH_NAME: ${{ steps.branch-check.outputs.name }}
run: |
if ! [[ -z "$BRANCH_NAME" ]]; then
git push --quiet origin --delete $BRANCH_NAME
echo "Deleted $BRANCH_NAME branch." | tee -a $GITHUB_STEP_SUMMARY
fi

View File

@@ -1,3 +1,4 @@
---
name: Crowdin Sync
on:
@@ -9,12 +10,12 @@ on:
jobs:
crowdin-sync:
name: Autosync
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
env:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -29,7 +30,7 @@ jobs:
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations
uses: crowdin/github-action@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
uses: crowdin/github-action@198daeb2d30636c4608d6a6bb96c009dbefc02a2 # v1.18.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@@ -1,3 +1,4 @@
---
name: Enforce PR labels
on:
@@ -6,7 +7,7 @@ on:
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2

View File

@@ -1,3 +1,4 @@
---
name: "Pull Request Labeler"
on:
@@ -9,8 +10,8 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
with:
sync-labels: true

View File

@@ -1,3 +1,4 @@
---
name: Release
run-name: Release ${{ inputs.release_type }}
@@ -22,12 +23,12 @@ on:
jobs:
release:
name: Create Release
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
outputs:
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
if: inputs.release_type != 'Dry Run'
if: github.event.inputs.release_type != 'Dry Run'
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
@@ -37,15 +38,15 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@main
with:
release-type: ${{ inputs.release_type }}
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
file: src/App/Platforms/Android/AndroidManifest.xml
file: src/Android/Properties/AndroidManifest.xml
- name: Get branch name
id: branch
@@ -54,7 +55,7 @@ jobs:
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create GitHub deployment
if: ${{ inputs.release_type != 'Dry Run' }}
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7
id: deployment
with:
@@ -64,34 +65,28 @@ jobs:
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
task: release
- name: Download all artifacts
if: ${{ inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
skip_unpack: true
- name: Dry Run - Download all artifacts
if: ${{ inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
branch: main
skip_unpack: true
- name: Unzip release assets
run: |
unzip bw-android-apk-sha256.txt.zip -d bw-android-apk-sha256.txt
unzip bw-fdroid-apk-sha256.txt.zip -d bw-fdroid-apk-sha256.txt
unzip com.x8bit.bitwarden-fdroid.apk.zip -d com.x8bit.bitwarden-fdroid.apk
unzip com.x8bit.bitwarden.aab.zip -d com.x8bit.bitwarden.aab
unzip com.x8bit.bitwarden.apk.zip -d com.x8bit.bitwarden.apk
- name: Prep Bitwarden iOS release asset
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release
if: ${{ inputs.release_type != 'Dry Run' }}
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
@@ -108,16 +103,16 @@ jobs:
draft: true
- name: Update deployment status to Success
if: ${{ inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure
if: ${{ inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'
@@ -126,36 +121,40 @@ jobs:
f-droid:
name: F-Droid Release
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
needs: release
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Download F-Droid .apk artifact
if: ${{ inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk
- name: Dry Run - Download F-Droid .apk artifact
if: ${{ inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@main
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
with:
workflow: build.yml
workflow_conclusion: success
branch: main
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: '16.x'
- name: Set up F-Droid server
run: pip install git+https://gitlab.com/fdroid/fdroidserver.git
run: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- name: Set up Git credentials
env:
@@ -168,85 +167,49 @@ jobs:
- name: Print environment
run: |
echo "Node Version: $(node --version)"
echo "NPM Version: $(npm --version)"
echo "Git Version: $(git --version)"
echo "F-Droid Server Version: $(fdroid --version)"
node --version
npm --version
git --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Install Node dependencies
run: npm install
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-mobile-fdroid"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
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: Setup git
run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Download secrets
- name: Decrypt secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name store_fdroid-keystore.jks --file ./store/fdroid/keystore.jks --output none
mkdir -p ~/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
- name: Compile for F-Droid Store
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
run: |
# Create required directories.
cd $GITHUB_WORKSPACE
mkdir dist
mkdir -p store/temp/fdroid
mkdir -p store/fdroid/repo
# Configure F-Droid server.
cp CNAME dist/
chmod 600 store/fdroid/config.yml store/fdroid/keystore.jks
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
echo "keypass: $FDROID_STORE_KEYSTORE_PASSWORD" >> store/fdroid/config.yml
echo "keystorepass: $FDROID_STORE_KEYSTORE_PASSWORD" >> store/fdroid/config.yml
echo "local_copy_dir: $TEMP_DIR" >> store/fdroid/config.yml
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk store/fdroid/repo/
# Run update and deploy.
cd store/fdroid
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk ./repo/
fdroid update
fdroid deploy
cd ../..
# Move files for distribution.
rm -rf store/temp/fdroid/archive
mv -v store/temp/fdroid dist
cp store/fdroid/index.html store/fdroid/btn.png store/fdroid/qr.png dist/fdroid
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages
if: ${{ inputs.release_type != 'Dry Run' }}
env:
TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-mobile-fdroid }}
run: |
git remote set-url origin https://git:${TOKEN}@github.com/${GITHUB_REPOSITORY}.git
npm run deploy -- -u "bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>"
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
run: npm run deploy

View File

@@ -1,3 +1,4 @@
---
name: 'Close stale issues and PRs'
on:
workflow_dispatch:
@@ -7,7 +8,7 @@ on:
jobs:
stale:
name: 'Check for stale issues and PRs'
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: 'Run stale action'
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0

View File

@@ -1,3 +1,4 @@
---
name: Auto Bump Mobile Version
on:
@@ -10,6 +11,24 @@ jobs:
name: Bump Mobile Version
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- 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
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
@@ -22,9 +41,9 @@ jobs:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Trigger Version Bump workflow
- name: "Bump version to ${{ steps.version.outputs.new_version }}"
env:
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
run: |
echo '{"cut_rc_branch": "false"}' | \
gh workflow run version-bump.yml --json --repo bitwarden/mobile
echo '{"cut_rc_branch": "false", "version_number": "${{ steps.version.outputs.new_version }}"}' | \
gh workflow run version-bump.yml --json --repo bitwarden/mobile

View File

@@ -1,44 +1,39 @@
---
name: Version Bump
run-name: Version Bump - v${{ inputs.version_number }}
on:
workflow_dispatch:
inputs:
version_number_override:
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
required: false
type: string
version_number:
description: "New version (example: '2024.1.0')"
required: true
cut_rc_branch:
description: "Cut RC branch?"
default: true
type: boolean
enable_slack_notification:
description: "Enable Slack notifications for upcoming release?"
default: false
type: boolean
jobs:
bump_version:
name: Bump Version
name: "Bump Version to v${{ inputs.version_number }}"
runs-on: ubuntu-22.04
outputs:
version: ${{ steps.set-final-version-output.outputs.version }}
steps:
- name: Validate version input
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-check@main
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
version: ${{ inputs.version_number_override }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Slack Notification Check
run: |
if [[ "${{ inputs.enable_slack_notification }}" == true ]]; then
echo "Slack notifications enabled."
else
echo "Slack notifications disabled."
fi
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout Branch
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
@@ -52,20 +47,6 @@ jobs:
exit 1
fi
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
with:
@@ -74,38 +55,25 @@ jobs:
git_user_signingkey: true
git_commit_gpgsign: true
- name: Setup git
run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Create Version Branch
id: create-branch
run: |
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
git switch -c $NAME
echo "name=$NAME" >> $GITHUB_OUTPUT
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Get current version
id: current-version
run: |
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
run: sudo apt install -y libxml2-utils
- name: Verify input version
if: ${{ inputs.version_number_override != '' }}
env:
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
NEW_VERSION: ${{ inputs.version_number_override }}
NEW_VERSION: ${{ inputs.version_number }}
run: |
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
# Error if version has not changed.
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
echo "Version has not changed."
@@ -121,93 +89,40 @@ jobs:
exit 1
fi
- name: Calculate next release version
if: ${{ inputs.version_number_override == '' }}
id: calculate-next-version
uses: bitwarden/gh-actions/version-next@main
with:
version: ${{ steps.current-version.outputs.version }}
- name: Bump Version - Android XML - Version Override
if: ${{ inputs.version_number_override != '' }}
id: bump-version-override
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ inputs.version_number_override }}
- name: Bump Version - Android XML - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
id: bump-version-automatic
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Autofill - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.Autofill
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Autofill - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Extension - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.Extension
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Extension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Extension - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Extension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.ShareExtension - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.ShareExtension
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.ShareExtension - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Set Job output
id: set-final-version-output
- name: Setup git
run: |
if [[ "${{ steps.bump-version-override.outcome }}" == "success" ]]; then
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
elif [[ "${{ steps.bump-version-automatic.outcome }}" == "success" ]]; then
echo "version=${{ steps.calculate-next-version.outputs.version }}" >> $GITHUB_OUTPUT
fi
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Check if version changed
id: version-changed
@@ -221,7 +136,7 @@ jobs:
- name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ steps.set-final-version-output.outputs.version }}" -a
run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
- name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
@@ -235,7 +150,7 @@ jobs:
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
TITLE: "Bump version to ${{ steps.set-final-version-output.outputs.version }}"
TITLE: "Bump version to ${{ inputs.version_number }}"
run: |
PR_URL=$(gh pr create --title "$TITLE" \
--base "main" \
@@ -251,57 +166,45 @@ jobs:
- [X] Other
## Objective
Automated version bump to ${{ steps.set-final-version-output.outputs.version }}")
Automated version bump to ${{ inputs.version_number }}")
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
- name: Approve PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr review $PR_NUMBER --approve
- name: Merge PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
- name: Report upcoming release version to Slack
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && inputs.enable_slack_notification == true }}
uses: bitwarden/gh-actions/report-upcoming-release-version@main
with:
version: ${{ steps.set-final-version-output.outputs.version }}
project: ${{ github.repository }}
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
cut_rc:
name: Cut RC branch
if: ${{ inputs.cut_rc_branch == true }}
needs: bump_version
if: ${{ inputs.cut_rc_branch == true }}
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
run: sudo apt install -y libxml2-utils
- name: Verify version has been updated
env:
NEW_VERSION: ${{ needs.bump_version.outputs.version }}
NEW_VERSION: ${{ inputs.version_number }}
run: |
# Wait for version to change.
while : ; do
echo "Waiting for version to be updated..."
git pull --force
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)

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

View File

@@ -1,15 +1,13 @@
<Project>
<PropertyGroup>
<MauiVersion>8.0.7</MauiVersion>
<MauiVersion>8.0.7-nightly.*</MauiVersion>
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
<IncludeBitwardenWatchOSApp>False</IncludeBitwardenWatchOSApp>
<IncludeBitwardenWatchOSApp>True</IncludeBitwardenWatchOSApp>
<Argon2IdLoadMtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</Argon2IdLoadMtouchExtraArgs>
<!-- Uncomment this when Unit Testing-->
<!-- <CustomConstants>UT</CustomConstants> -->
<!-- Uncomment this when building FDROID-->
<!-- <CustomConstants>FDROID</CustomConstants> -->
</PropertyGroup>
</Project>
</Project>

View File

@@ -4,10 +4,6 @@
# Bitwarden Mobile Application
> [!TIP]
> Looking for the new native apps? Head on over to [bitwarden/android](https://github.com/bitwarden/android) and [bitwarden/ios](https://github.com/bitwarden/ios)
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
The Bitwarden mobile application is written in C# using .NET MAUI.
@@ -16,7 +12,7 @@ The Bitwarden mobile application is written in C# using .NET MAUI.
# Build/Run
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/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.
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.
# We're Hiring!

View File

@@ -15,18 +15,16 @@ abstract record VariantConfig(
string AppName,
string AndroidPackageName,
string iOSBundleId,
string ApsEnvironment,
string DistProvisioningProfilePrefix
string ApsEnvironment
);
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
//NOTE: Beta iOS variants have a different ITSEncryptionExportComplianceCode
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development", "Dist:");
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development", "Dist:");
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production", "Dist: Beta");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production", "Dist:");
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(),
@@ -199,8 +197,7 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
var prevBundleId = plist["CFBundleIdentifier"];
var prevBundleName = plist["CFBundleName"];
//var newVersion = CreateBuildNumber(prevVersion).ToString();
// we need to maintain version formatting here composed of one to three period-separated integers, so we cannot use the GetVersionName method as in Android for non-Prod.
var newVersionName = prevVersionName;
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
var newBundleId = GetiOSBundleId(buildVariant, projectType);
var newBundleName = GetiOSBundleName(buildVariant, projectType);
@@ -222,11 +219,6 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
}
if(buildVariant is Beta)
{
plist["ITSEncryptionExportComplianceCode"] = "3dd3e32f-efa6-4d99-b410-28aa28b1cb77";
}
SerializePlist(plistFile, plist);
Information($"Changed app name from {prevBundleName} to {newBundleName}");
@@ -236,15 +228,12 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
Information($"{plistPath} updated with success!");
}
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant, bool updateApsEnv)
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
{
var EntitlementlistFile = File(entitlementsPath);
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
if (updateApsEnv)
{
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
}
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}" };;
@@ -283,10 +272,9 @@ private void UpdateWatchPbxproj(string pbxprojPath, string newVersion)
const string pattern = @"MARKETING_VERSION = [^;]*;";
fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};");
FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified Marketing Version successfully.");
FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified successfully.");
}
/// <summary>
@@ -339,7 +327,7 @@ Task("UpdateiOSPlist")
var infoPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant, true);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSAutofillPlist")
@@ -350,7 +338,7 @@ Task("UpdateiOSAutofillPlist")
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, false);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSExtensionPlist")
@@ -361,7 +349,7 @@ Task("UpdateiOSExtensionPlist")
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, false);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSShareExtensionPlist")
@@ -372,7 +360,7 @@ Task("UpdateiOSShareExtensionPlist")
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, false);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSCodeFiles")
@@ -409,22 +397,6 @@ Task("UpdateWatchKitAppInfoPlist")
UpdateWatchKitAppInfoPlist(infoPath, buildVariant);
});
Task("UpdateDistProfiles")
.IsDependentOn("UpdateiOSCodeFiles")
.Does(()=> {
var buildVariant = GetVariant();
var filesToReplace = new string[] {
Path.Combine(".github", "resources", "export-options-app-store.plist"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj")
};
foreach(string path in filesToReplace)
{
ReplaceInFile(path, "Dist:", buildVariant.DistProvisioningProfilePrefix);
}
});
#endregion iOS
#region Main Tasks
@@ -446,7 +418,6 @@ Task("iOS")
.IsDependentOn("UpdateiOSCodeFiles")
.IsDependentOn("UpdateWatchProject")
.IsDependentOn("UpdateWatchKitAppInfoPlist")
.IsDependentOn("UpdateDistProfiles")
.Does(()=>
{
Information("iOS app updated");
@@ -466,4 +437,4 @@ Options:
});
#endregion Main Tasks
RunTarget(target);
RunTarget(target);

View File

@@ -1,6 +0,0 @@
{
"sdk": {
"version": "8.0.402",
"rollForward": "disable"
}
}

854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,6 @@
"clean:l10n": "git push origin --delete l10n_master"
},
"devDependencies": {
"gh-pages": "^6.1.1"
"gh-pages": "3.2.3"
}
}

View File

@@ -44,7 +44,7 @@
<AndroidEnableMultiDex>True</AndroidEnableMultiDex>
<UseInterpreter>False</UseInterpreter>
<DebugSymbols>False</DebugSymbols>
<RunAOTCompilation>True</RunAOTCompilation>
<RunAOTCompilation>False</RunAOTCompilation>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
@@ -117,8 +117,6 @@
<Folder Include="Platforms\Android\Services\" />
<Folder Include="Platforms\Android\Tiles\" />
<Folder Include="Platforms\Android\Utilities\" />
<Folder Include="Platforms\Android\Resources\drawable-xxxhdpi\" />
<Folder Include="Resources\Raw\" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
@@ -259,13 +257,5 @@
<None Remove="Platforms\iOS\Resources\more_vert.png" />
<None Remove="Platforms\iOS\Resources\logo_white.png" />
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
<None Remove="Platforms\Android\Resources\drawable-xxxhdpi\" />
<None Remove="Resources\Raw\" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<BundleResource Include="Platforms\iOS\PrivacyInfo.xcprivacy" LogicalName="PrivacyInfo.xcprivacy" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<MauiAsset Include="Resources\Raw\fido2_privileged_allow_list.json" LogicalName="fido2_privileged_allow_list.json" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,5 @@
<?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="2024.10.111" 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="2024.2.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
@@ -43,9 +43,6 @@
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />

View File

@@ -347,7 +347,7 @@ namespace Bit.Droid.Autofill
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
// "my vault" presentation) so we're including an empty one here
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, false));
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
}
var slice = CreateInlinePresentationSlice(
inlinePresentationSpec,

View File

@@ -1,321 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Nodes;
using Android.App;
using Android.Content;
using Android.OS;
using AndroidX.Credentials;
using AndroidX.Credentials.Exceptions;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.App.Abstractions;
using Bit.App.Droid.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Utilities.Fido2.Extensions;
using Bit.Droid;
using Org.Json;
using Activity = Android.App.Activity;
using Drawables = Android.Graphics.Drawables;
namespace Bit.App.Platforms.Android.Autofill
{
public static class CredentialHelpers
{
public static async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
BeginGetPublicKeyCredentialOption option, Context context, bool hasVaultBeenUnlockedInThisTransaction)
{
var passkeyEntries = new List<CredentialEntry>();
var requestOptions = new PublicKeyCredentialRequestOptions(option.RequestJson);
var authenticator = Bit.Core.Utilities.ServiceContainer.Resolve<IFido2AuthenticatorService>();
var credentials = await authenticator.SilentCredentialDiscoveryAsync(requestOptions.RpId);
// We need to change the request code for every pending intent on mapping the credential so the extras are not overriten by the last
// credential entry created.
int requestCodeAddition = 0;
passkeyEntries = credentials.Select(credential => MapCredential(credential, option, context, hasVaultBeenUnlockedInThisTransaction, Bit.Droid.Autofill.CredentialProviderService.UniqueGetRequestCode + requestCodeAddition++) as CredentialEntry).ToList();
return passkeyEntries;
}
private static PublicKeyCredentialEntry MapCredential(Fido2AuthenticatorDiscoverableCredentialMetadata credential, BeginGetPublicKeyCredentialOption option, Context context, bool hasVaultBeenUnlockedInThisTransaction, int requestCode)
{
var credDataBundle = new Bundle();
credDataBundle.PutByteArray(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialIdIntentExtra, credential.Id);
var intent = new Intent(context, typeof(Bit.Droid.Autofill.CredentialProviderSelectionActivity))
.SetAction(Bit.Droid.Autofill.CredentialProviderService.GetFido2IntentAction).SetPackage(Constants.PACKAGE_NAME);
intent.PutExtra(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
intent.PutExtra(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialProviderCipherId, credential.CipherId);
intent.PutExtra(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialHasVaultBeenUnlockedInThisTransactionExtra, hasVaultBeenUnlockedInThisTransaction);
var pendingIntent = PendingIntent.GetActivity(context, requestCode, intent,
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
return new PublicKeyCredentialEntry.Builder(
context,
credential.UserName ?? "No username",
pendingIntent,
option)
.SetDisplayName(credential.UserName ?? "No username")
.SetIcon(Drawables.Icon.CreateWithResource(context, Microsoft.Maui.Resource.Drawable.icon))
.Build();
}
private static PublicKeyCredentialCreationOptions GetPublicKeyCredentialCreationOptionsFromJson(string json)
{
var request = new PublicKeyCredentialCreationOptions(json);
var jsonObj = new JSONObject(json);
var authenticatorSelection = jsonObj.GetJSONObject("authenticatorSelection");
request.AuthenticatorSelection = new AndroidX.Credentials.WebAuthn.AuthenticatorSelectionCriteria(
authenticatorSelection.OptString("authenticatorAttachment", "platform"),
authenticatorSelection.OptString("residentKey", null),
authenticatorSelection.OptBoolean("requireResidentKey", false),
authenticatorSelection.OptString("userVerification", "preferred"));
return request;
}
public static async Task CreateCipherPasskeyAsync(ProviderCreateCredentialRequest getRequest, Activity activity)
{
var callingRequest = getRequest?.CallingRequest as CreatePublicKeyCredentialRequest;
if (callingRequest is null)
{
await DisplayAlertAsync(AppResources.AnErrorHasOccurred, string.Empty);
FailAndFinish();
return;
}
var credentialCreationOptions = GetPublicKeyCredentialCreationOptionsFromJson(callingRequest.RequestJson);
string origin;
try
{
origin = await ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, credentialCreationOptions.Rp.Id);
}
catch (Core.Exceptions.ValidationException valEx)
{
await DisplayAlertAsync(AppResources.AnErrorHasOccurred, valEx.Message);
FailAndFinish();
return;
}
if (origin is null)
{
await DisplayAlertAsync(AppResources.ErrorCreatingPasskey, AppResources.PasskeysNotSupportedForThisApp);
FailAndFinish();
return;
}
var rp = new Core.Utilities.Fido2.PublicKeyCredentialRpEntity()
{
Id = credentialCreationOptions.Rp.Id,
Name = credentialCreationOptions.Rp.Name
};
var user = new Core.Utilities.Fido2.PublicKeyCredentialUserEntity()
{
Id = credentialCreationOptions.User.GetId(),
Name = credentialCreationOptions.User.Name,
DisplayName = credentialCreationOptions.User.DisplayName
};
var pubKeyCredParams = new List<Core.Utilities.Fido2.PublicKeyCredentialParameters>();
foreach (var pubKeyCredParam in credentialCreationOptions.PubKeyCredParams)
{
pubKeyCredParams.Add(new Core.Utilities.Fido2.PublicKeyCredentialParameters() { Alg = Convert.ToInt32(pubKeyCredParam.Alg), Type = pubKeyCredParam.Type });
}
var excludeCredentials = new List<Core.Utilities.Fido2.PublicKeyCredentialDescriptor>();
foreach (var excludeCred in credentialCreationOptions.ExcludeCredentials)
{
excludeCredentials.Add(new Core.Utilities.Fido2.PublicKeyCredentialDescriptor() { Id = excludeCred.GetId(), Type = excludeCred.Type, Transports = excludeCred.Transports.ToArray() });
}
var authenticatorSelection = new Core.Utilities.Fido2.AuthenticatorSelectionCriteria()
{
UserVerification = credentialCreationOptions.AuthenticatorSelection.UserVerification,
ResidentKey = credentialCreationOptions.AuthenticatorSelection.ResidentKey,
RequireResidentKey = credentialCreationOptions.AuthenticatorSelection.RequireResidentKey
};
var timeout = Convert.ToInt32(credentialCreationOptions.Timeout);
var credentialCreateParams = new Fido2ClientCreateCredentialParams()
{
Challenge = credentialCreationOptions.GetChallenge(),
Origin = origin,
PubKeyCredParams = pubKeyCredParams.ToArray(),
Rp = rp,
User = user,
Timeout = timeout,
Attestation = credentialCreationOptions.Attestation,
AuthenticatorSelection = authenticatorSelection,
ExcludeCredentials = excludeCredentials.ToArray(),
Extensions = MapExtensionsFromJson(credentialCreationOptions),
SameOriginWithAncestors = true
};
var credentialExtraCreateParams = new Fido2ExtraCreateCredentialParams
(
callingRequest.GetClientDataHash(),
getRequest.CallingAppInfo?.PackageName
);
var fido2MediatorService = ServiceContainer.Resolve<IFido2MediatorService>();
var clientCreateCredentialResult = await fido2MediatorService.CreateCredentialAsync(credentialCreateParams, credentialExtraCreateParams);
if (clientCreateCredentialResult == null)
{
FailAndFinish();
return;
}
var transportsArray = new JSONArray();
if (clientCreateCredentialResult.Transports != null)
{
foreach (var transport in clientCreateCredentialResult.Transports)
{
transportsArray.Put(transport);
}
}
var responseInnerAndroidJson = new JSONObject();
if (clientCreateCredentialResult.ClientDataJSON != null)
{
responseInnerAndroidJson.Put("clientDataJSON", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.ClientDataJSON));
}
responseInnerAndroidJson.Put("authenticatorData", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.AuthData));
responseInnerAndroidJson.Put("attestationObject", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.AttestationObject));
responseInnerAndroidJson.Put("transports", transportsArray);
responseInnerAndroidJson.Put("publicKeyAlgorithm", clientCreateCredentialResult.PublicKeyAlgorithm);
responseInnerAndroidJson.Put("publicKey", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.PublicKey));
var rootAndroidJson = new JSONObject();
rootAndroidJson.Put("id", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.CredentialId));
rootAndroidJson.Put("rawId", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.CredentialId));
rootAndroidJson.Put("authenticatorAttachment", "platform");
rootAndroidJson.Put("type", "public-key");
rootAndroidJson.Put("clientExtensionResults", MapExtensionsToJson(clientCreateCredentialResult.Extensions));
rootAndroidJson.Put("response", responseInnerAndroidJson);
var result = new Intent();
var publicKeyResponse = new CreatePublicKeyCredentialResponse(rootAndroidJson.ToString());
PendingIntentHandler.SetCreateCredentialResponse(result, publicKeyResponse);
activity.SetResult(Result.Ok, result);
activity.Finish();
async Task DisplayAlertAsync(string title, string message)
{
if (ServiceContainer.TryResolve<IDeviceActionService>(out var deviceActionService))
{
await deviceActionService.DisplayAlertAsync(title, message, AppResources.Ok);
}
}
void FailAndFinish()
{
var result = new Intent();
PendingIntentHandler.SetCreateCredentialException(result, new CreateCredentialUnknownException());
activity.SetResult(Result.Ok, result);
activity.Finish();
}
}
private static Fido2CreateCredentialExtensionsParams MapExtensionsFromJson(PublicKeyCredentialCreationOptions options)
{
if (options == null || !options.Json.Has("extensions"))
{
return null;
}
var extensions = options.Json.GetJSONObject("extensions");
return new Fido2CreateCredentialExtensionsParams
{
CredProps = extensions.Has("credProps") && extensions.GetBoolean("credProps")
};
}
private static JSONObject MapExtensionsToJson(Fido2CreateCredentialExtensionsResult extensions)
{
if (extensions == null)
{
return null;
}
var extensionsJson = new JSONObject();
if (extensions.CredProps != null)
{
var credPropsJson = new JSONObject();
credPropsJson.Put("rk", extensions.CredProps.Rk);
extensionsJson.Put("credProps", credPropsJson);
}
return extensionsJson;
}
public static async Task<string> LoadFido2PrivilegedAllowedListAsync()
{
try
{
using var stream = await FileSystem.OpenAppPackageFileAsync("fido2_privileged_allow_list.json");
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
catch
{
return null;
}
}
public static async Task<string> ValidateCallingAppInfoAndGetOriginAsync(CallingAppInfo callingAppInfo, string rpId)
{
if (callingAppInfo.Origin is null)
{
return await ValidateAssetLinksAndGetOriginAsync(callingAppInfo, rpId);
}
var privilegedAllowedList = await LoadFido2PrivilegedAllowedListAsync();
if (privilegedAllowedList is null)
{
throw new InvalidOperationException("Could not load Fido2 privileged allowed list");
}
if (!privilegedAllowedList.Contains($"\"package_name\": \"{callingAppInfo.PackageName}\""))
{
throw new Core.Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseBrowserIsNotPrivileged);
}
try
{
return callingAppInfo.GetOrigin(privilegedAllowedList);
}
catch (Java.Lang.IllegalStateException)
{
throw new Core.Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseBrowserSignatureDoesNotMatch);
}
catch (Java.Lang.IllegalArgumentException)
{
return null; // wrong list format
}
}
private static async Task<string> ValidateAssetLinksAndGetOriginAsync(CallingAppInfo callingAppInfo, string rpId)
{
if (!ServiceContainer.TryResolve<IAssetLinksService>(out var assetLinksService))
{
throw new InvalidOperationException("Can't resolve IAssetLinksService");
}
var normalizedFingerprint = callingAppInfo.GetLatestCertificationFingerprint();
var isValid = await assetLinksService.ValidateAssetLinksAsync(rpId, callingAppInfo.PackageName, normalizedFingerprint);
return isValid ? callingAppInfo.GetAndroidOrigin() : null;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.Droid.Autofill
{
public class CredentialProviderConstants
{
public const string CredentialProviderCipherId = "credentialProviderCipherId";
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
public const string CredentialIdIntentExtra = "credId";
}
}

View File

@@ -1,20 +1,12 @@
using Android.App;
using Android.Content;
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Credentials;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.App.Droid.Utilities;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Services;
using Bit.App.Platforms.Android.Autofill;
using AndroidX.Credentials.Exceptions;
using Org.Json;
using Bit.App.Droid.Utilities;
namespace Bit.Droid.Autofill
{
@@ -23,14 +15,6 @@ namespace Bit.Droid.Autofill
LaunchMode = LaunchMode.SingleTop)]
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
{
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
private LazyResolve<IFido2AndroidGetAssertionUserInterface> _fido2GetAssertionUserInterface = new LazyResolve<IFido2AndroidGetAssertionUserInterface>();
private LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
private LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
private LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
@@ -39,145 +23,43 @@ namespace Bit.Droid.Autofill
var cipherId = Intent?.GetStringExtra(CredentialProviderConstants.CredentialProviderCipherId);
if (string.IsNullOrEmpty(cipherId))
{
SetResult(Result.Canceled);
Finish();
return;
}
GetCipherAndPerformFido2AuthAsync(cipherId).FireAndForget();
GetCipherAndPerformPasskeyAuthAsync(cipherId).FireAndForget();
}
//Used to avoid crash on MAUI when doing back
public override void OnBackPressed()
private async Task GetCipherAndPerformPasskeyAuthAsync(string cipherId)
{
Finish();
}
// TODO this is a work in progress
// https://developer.android.com/training/sign-in/credential-provider#passkeys-implement
private async Task GetCipherAndPerformFido2AuthAsync(string cipherId)
{
string RpId = string.Empty;
try
{
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
// var publicKeyRequest = getRequest?.CredentialOptions as PublicKeyCredentialRequestOptions;
if (getRequest is null)
{
FailAndFinish();
return;
}
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
var credIdEnc = requestInfo?.GetString(CredentialProviderConstants.CredentialIdIntentExtra);
var credentialOption = getRequest.CredentialOptions.FirstOrDefault();
var credentialPublic = credentialOption as GetPublicKeyCredentialOption;
var cipherService = ServiceContainer.Resolve<ICipherService>();
var cipher = await cipherService.GetAsync(cipherId);
var decCipher = await cipher.DecryptAsync();
var requestOptions = new PublicKeyCredentialRequestOptions(credentialPublic.RequestJson);
RpId = requestOptions.RpId;
var passkey = decCipher.Login.Fido2Credentials.Find(f => f.CredentialId == credIdEnc);
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
var credentialId = requestInfo?.GetByteArray(CredentialProviderConstants.CredentialIdIntentExtra);
var hasVaultBeenUnlockedInThisTransaction = Intent.GetBooleanExtra(CredentialProviderConstants.CredentialHasVaultBeenUnlockedInThisTransactionExtra, false);
var credId = Convert.FromBase64String(credIdEnc);
// var privateKey = Convert.FromBase64String(passkey.PrivateKey);
// var uid = Convert.FromBase64String(passkey.uid);
var packageName = getRequest.CallingAppInfo.PackageName;
var origin = getRequest?.CallingAppInfo.Origin;
var packageName = getRequest?.CallingAppInfo.PackageName;
string origin;
try
{
origin = await CredentialHelpers.ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, RpId);
}
catch (Core.Exceptions.ValidationException valEx)
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.AnErrorHasOccurred, valEx.Message, AppResources.Ok);
FailAndFinish();
return;
}
// --- continue WIP here (save TOTP copy as last step) ---
if (origin is null)
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok);
FailAndFinish();
return;
}
_fido2GetAssertionUserInterface.Value.Init(
cipherId,
false,
() => hasVaultBeenUnlockedInThisTransaction,
RpId
);
var clientAssertParams = new Fido2ClientAssertCredentialParams
{
Challenge = requestOptions.GetChallenge(),
RpId = RpId,
AllowCredentials = new Core.Utilities.Fido2.PublicKeyCredentialDescriptor[] { new Core.Utilities.Fido2.PublicKeyCredentialDescriptor { Id = credentialId } },
Origin = origin,
SameOriginWithAncestors = true,
UserVerification = requestOptions.UserVerification
};
var extraAssertParams = new Fido2ExtraAssertCredentialParams
(
getRequest.CallingAppInfo.Origin != null ? credentialPublic.GetClientDataHash() : null,
packageName
);
var assertResult = await _fido2MediatorService.Value.AssertCredentialAsync(clientAssertParams, extraAssertParams);
var result = new Intent();
var responseInnerAndroidJson = new JSONObject();
if (assertResult.ClientDataJSON != null)
{
responseInnerAndroidJson.Put("clientDataJSON", CoreHelpers.Base64UrlEncode(assertResult.ClientDataJSON));
}
responseInnerAndroidJson.Put("authenticatorData", CoreHelpers.Base64UrlEncode(assertResult.AuthenticatorData));
responseInnerAndroidJson.Put("signature", CoreHelpers.Base64UrlEncode(assertResult.Signature));
responseInnerAndroidJson.Put("userHandle", CoreHelpers.Base64UrlEncode(assertResult.SelectedCredential.UserHandle));
var rootAndroidJson = new JSONObject();
rootAndroidJson.Put("id", CoreHelpers.Base64UrlEncode(assertResult.SelectedCredential.Id));
rootAndroidJson.Put("rawId", CoreHelpers.Base64UrlEncode(assertResult.SelectedCredential.Id));
rootAndroidJson.Put("authenticatorAttachment", "platform");
rootAndroidJson.Put("type", "public-key");
rootAndroidJson.Put("clientExtensionResults", new JSONObject());
rootAndroidJson.Put("response", responseInnerAndroidJson);
var json = rootAndroidJson.ToString();
var cred = new PublicKeyCredential(json);
var credResponse = new GetCredentialResponse(cred);
PendingIntentHandler.SetGetCredentialResponse(result, credResponse);
await MainThread.InvokeOnMainThreadAsync(() =>
{
SetResult(Result.Ok, result);
Finish();
});
}
catch (NotAllowedError)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, string.Format(AppResources.ThereWasAProblemReadingAPasskeyForXTryAgainLater, RpId), AppResources.Ok);
FailAndFinish();
});
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, string.Format(AppResources.ThereWasAProblemReadingAPasskeyForXTryAgainLater, RpId), AppResources.Ok);
FailAndFinish();
});
}
}
private void FailAndFinish()
{
var result = new Intent();
PendingIntentHandler.SetGetCredentialException(result, new GetCredentialUnknownException());
SetResult(Result.Ok, result);
Finish();
// Copy TOTP if needed
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
autofillHandler.Autofill(decCipher);
}
}
}

View File

@@ -1,15 +1,16 @@
using Android;
using Android.App;
using Android.Content;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using AndroidX.Credentials.Provider;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using AndroidX.Credentials.Exceptions;
using Bit.App.Droid.Utilities;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities.Fido2;
using AndroidX.Credentials.WebAuthn;
using Bit.Core.Models.View;
using Resource = Microsoft.Maui.Resource;
namespace Bit.Droid.Autofill
{
@@ -19,123 +20,62 @@ namespace Bit.Droid.Autofill
[Register("com.x8bit.bitwarden.Autofill.CredentialProviderService")]
public class CredentialProviderService : AndroidX.Credentials.Provider.CredentialProviderService
{
public const string GetFido2IntentAction = "PACKAGE_NAME.GET_PASSKEY";
public const string CreateFido2IntentAction = "PACKAGE_NAME.CREATE_PASSKEY";
public const int UniqueGetRequestCode = 94556023;
public const int UniqueCreateRequestCode = 94556024;
private const string GetPasskeyIntentAction = "PACKAGE_NAME.GET_PASSKEY";
private const int UniqueRequestCode = 94556023;
private readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
private ICipherService _cipherService;
private IUserVerificationService _userVerificationService;
private IVaultTimeoutService _vaultTimeoutService;
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public override async void OnBeginCreateCredentialRequest(BeginCreateCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
try
{
var response = await ProcessCreateCredentialsRequestAsync(request);
if (response != null)
{
await MainThread.InvokeOnMainThreadAsync(() => callback.OnResult(response));
return;
}
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
MainThread.BeginInvokeOnMainThread(() => callback.OnError(AppResources.ErrorCreatingPasskey));
}
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
public override async void OnBeginGetCredentialRequest(BeginGetCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
try
{
await _vaultTimeoutService.Value.CheckVaultTimeoutAsync();
var locked = await _vaultTimeoutService.Value.IsLockedAsync();
_vaultTimeoutService ??= ServiceContainer.Resolve<IVaultTimeoutService>();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
var locked = await _vaultTimeoutService.IsLockedAsync();
if (!locked)
{
var response = await ProcessGetCredentialsRequestAsync(request);
callback.OnResult(response);
return;
}
var intent = new Intent(ApplicationContext, typeof(MainActivity));
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialGet);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueGetRequestCode, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true));
var unlockAction = new AuthenticationAction(AppResources.Unlock, pendingIntent);
var unlockResponse = new BeginGetCredentialResponse.Builder()
.SetAuthenticationActions(new List<AuthenticationAction>() { unlockAction } )
.Build();
callback.OnResult(unlockResponse);
// TODO handle auth/unlock account flow
}
catch (GetCredentialException e)
{
_logger.Value.Exception(e);
callback.OnError(e.ErrorMessage ?? AppResources.ErrorReadingPasskey);
callback.OnError(e.ErrorMessage ?? "Error getting credentials");
}
catch (Exception e)
{
_logger.Value.Exception(e);
callback.OnError(AppResources.ErrorReadingPasskey);
throw;
}
}
private async Task<BeginCreateCredentialResponse> ProcessCreateCredentialsRequestAsync(
BeginCreateCredentialRequest request)
{
if (request == null) { return null; }
if (request is BeginCreatePasswordCredentialRequest beginCreatePasswordCredentialRequest)
{
//This flow can be used if Password flow needs to be implemented
throw new NotImplementedException();
//return HandleCreatePasswordQuery(beginCreatePasswordCredentialRequest);
}
else if (request is BeginCreatePublicKeyCredentialRequest beginCreatePublicKeyCredentialRequest)
{
return await HandleCreatePasskeyQueryAsync(beginCreatePublicKeyCredentialRequest);
}
return null;
}
private async Task<BeginCreateCredentialResponse> HandleCreatePasskeyQueryAsync(BeginCreatePublicKeyCredentialRequest optionRequest)
{
var intent = new Intent(ApplicationContext, typeof(MainActivity));
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialCreate);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueCreateRequestCode, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true));
var userEmail = await GetSafeActiveAccountEmailAsync();
var createEntryBuilder = new CreateEntry.Builder(userEmail ?? AppResources.Bitwarden, pendingIntent)
.SetDescription(userEmail != null
? string.Format(AppResources.YourPasskeyWillBeSavedToYourBitwardenVaultForX, userEmail)
: AppResources.YourPasskeyWillBeSavedToYourBitwardenVault)
.Build();
var createCredentialResponse = new BeginCreateCredentialResponse.Builder()
.AddCreateEntry(createEntryBuilder);
return createCredentialResponse.Build();
}
private async Task<BeginGetCredentialResponse> ProcessGetCredentialsRequestAsync(
BeginGetCredentialRequest request)
{
var credentialEntries = new List<CredentialEntry>();
IList<CredentialEntry> credentialEntries = null;
foreach (var option in request.BeginGetCredentialOptions.OfType<BeginGetPublicKeyCredentialOption>())
foreach (var option in request.BeginGetCredentialOptions)
{
credentialEntries.AddRange(await Bit.App.Platforms.Android.Autofill.CredentialHelpers.PopulatePasskeyDataAsync(request.CallingAppInfo, option, ApplicationContext, false));
var credentialOption = option as BeginGetPublicKeyCredentialOption;
if (credentialOption != null)
{
credentialEntries ??= new List<CredentialEntry>();
((List<CredentialEntry>)credentialEntries).AddRange(
await PopulatePasskeyDataAsync(request.CallingAppInfo, credentialOption));
}
}
if (!credentialEntries.Any())
if (credentialEntries == null)
{
return new BeginGetCredentialResponse();
}
@@ -145,24 +85,63 @@ namespace Bit.Droid.Autofill
.Build();
}
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
private async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
BeginGetPublicKeyCredentialOption option)
{
callback.OnResult(null);
var packageName = callingAppInfo.PackageName;
var origin = callingAppInfo.Origin;
var signingInfo = callingAppInfo.SigningInfo;
var request = new PublicKeyCredentialRequestOptions(option.RequestJson);
var passkeyEntries = new List<CredentialEntry>();
_cipherService ??= ServiceContainer.Resolve<ICipherService>();
var ciphers = await _cipherService.GetAllDecryptedForUrlAsync(origin);
if (ciphers == null)
{
return passkeyEntries;
}
var passkeyCiphers = ciphers.Where(cipher => cipher.HasFido2Credential).ToList();
if (!passkeyCiphers.Any())
{
return passkeyEntries;
}
foreach (var cipher in passkeyCiphers)
{
var passkeyEntry = GetPasskey(cipher, option);
passkeyEntries.Add(passkeyEntry);
}
return passkeyEntries;
}
private async Task<string> GetSafeActiveAccountEmailAsync()
private PublicKeyCredentialEntry GetPasskey(CipherView cipher, BeginGetPublicKeyCredentialOption option)
{
try
{
return await _stateService.Value.GetEmailAsync();
}
catch (Exception ex)
{
// if it throws to get the user's email then we log and continue showing a more generic message
_logger.Value.Exception(ex);
return null;
}
var credDataBundle = new Bundle();
credDataBundle.PutString(CredentialProviderConstants.CredentialIdIntentExtra,
cipher.Login.MainFido2Credential.CredentialId);
var intent = new Intent(ApplicationContext, typeof(CredentialProviderSelectionActivity))
.SetAction(GetPasskeyIntentAction).SetPackage(Constants.PACKAGE_NAME);
intent.PutExtra(CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
intent.PutExtra(CredentialProviderConstants.CredentialProviderCipherId, cipher.Id);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueRequestCode, intent,
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
return new PublicKeyCredentialEntry.Builder(
ApplicationContext,
cipher.Login.Username ?? "No username",
pendingIntent,
option)
.SetDisplayName(cipher.Name)
.SetIcon(Icon.CreateWithResource(ApplicationContext, Resource.Drawable.icon))
.Build();
}
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
}
}

View File

@@ -1,77 +0,0 @@
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
namespace Bit.App.Platforms.Android.Autofill
{
public interface IFido2AndroidGetAssertionUserInterface : IFido2GetAssertionUserInterface
{
void Init(string cipherId,
bool userVerified,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
string rpId);
}
public class Fido2GetAssertionUserInterface : Core.Utilities.Fido2.Fido2GetAssertionUserInterface, IFido2AndroidGetAssertionUserInterface
{
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
public Fido2GetAssertionUserInterface(IStateService stateService,
IVaultTimeoutService vaultTimeoutService,
ICipherService cipherService,
IUserVerificationMediatorService userVerificationMediatorService)
{
_stateService = stateService;
_vaultTimeoutService = vaultTimeoutService;
_cipherService = cipherService;
_userVerificationMediatorService = userVerificationMediatorService;
}
public void Init(string cipherId,
bool userVerified,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
string rpId)
{
Init(cipherId,
userVerified,
EnsureAuthenAndVaultUnlockedAsync,
hasVaultBeenUnlockedInThisTransaction,
(cipherId, userVerificationPreference) => VerifyUserAsync(cipherId, userVerificationPreference, rpId, hasVaultBeenUnlockedInThisTransaction()));
}
public async Task EnsureAuthenAndVaultUnlockedAsync()
{
if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync())
{
// this should never happen but just in case.
throw new InvalidOperationException("Not authed or vault locked");
}
}
private async Task<bool> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction)
{
try
{
var encrypted = await _cipherService.GetAsync(selectedCipherId);
var cipher = await encrypted.DecryptAsync();
var userVerification = await _userVerificationMediatorService.VerifyUserForFido2Async(
new Fido2UserVerificationOptions(
cipher?.Reprompt == Core.Enums.CipherRepromptType.Password,
userVerificationPreference,
vaultUnlockedDuringThisTransaction,
rpId)
);
return !userVerification.IsCancelled && userVerification.Result;
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return false;
}
}
}
}

View File

@@ -1,202 +0,0 @@
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.App.Platforms.Android.Autofill
{
public class Fido2MakeCredentialUserInterface : IFido2MakeCredentialConfirmationUserInterface
{
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private LazyResolve<IMessagingService> _messagingService = new LazyResolve<IMessagingService>();
private TaskCompletionSource<(string cipherId, bool? userVerified)> _confirmCredentialTcs;
private TaskCompletionSource<bool> _unlockVaultTcs;
private Fido2UserVerificationOptions? _currentDefaultUserVerificationOptions;
private Func<bool> _checkHasVaultBeenUnlockedInThisTransaction;
public Fido2MakeCredentialUserInterface(IStateService stateService,
IVaultTimeoutService vaultTimeoutService,
ICipherService cipherService,
IUserVerificationMediatorService userVerificationMediatorService,
IDeviceActionService deviceActionService,
IPlatformUtilsService platformUtilsService)
{
_stateService = stateService;
_vaultTimeoutService = vaultTimeoutService;
_cipherService = cipherService;
_userVerificationMediatorService = userVerificationMediatorService;
_deviceActionService = deviceActionService;
_platformUtilsService = platformUtilsService;
}
public bool HasVaultBeenUnlockedInThisTransaction => _checkHasVaultBeenUnlockedInThisTransaction?.Invoke() == true;
public bool IsConfirmingNewCredential => _confirmCredentialTcs?.Task != null && !_confirmCredentialTcs.Task.IsCompleted;
public bool IsWaitingUnlockVault => _unlockVaultTcs?.Task != null && !_unlockVaultTcs.Task.IsCompleted;
public async Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams)
{
_confirmCredentialTcs?.TrySetCanceled();
_confirmCredentialTcs = null;
_confirmCredentialTcs = new TaskCompletionSource<(string cipherId, bool? userVerified)>();
_currentDefaultUserVerificationOptions = new Fido2UserVerificationOptions(false, confirmNewCredentialParams.UserVerificationPreference, HasVaultBeenUnlockedInThisTransaction, confirmNewCredentialParams.RpId);
_messagingService.Value.Send(Bit.Core.Constants.CredentialNavigateToAutofillCipherMessageCommand, confirmNewCredentialParams);
var (cipherId, isUserVerified) = await _confirmCredentialTcs.Task;
var verified = isUserVerified;
if (verified is null)
{
var userVerification = await VerifyUserAsync(cipherId, confirmNewCredentialParams.UserVerificationPreference, confirmNewCredentialParams.RpId);
// TODO: If cancelled then let the user choose another cipher.
// I think this can be done by showing a message to the uesr and recursive calling of this method ConfirmNewCredentialAsync
verified = !userVerification.IsCancelled && userVerification.Result;
}
if (cipherId is null)
{
return await CreateNewLoginForFido2CredentialAsync(confirmNewCredentialParams, verified.Value);
}
return (cipherId, verified.Value);
}
private async Task<(string CipherId, bool UserVerified)> CreateNewLoginForFido2CredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams, bool userVerified)
{
if (!userVerified && await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(new Fido2UserVerificationOptions
(
false,
confirmNewCredentialParams.UserVerificationPreference,
true,
confirmNewCredentialParams.RpId
)))
{
return (null, false);
}
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var cipherId = await _cipherService.CreateNewLoginForPasskeyAsync(confirmNewCredentialParams);
await _deviceActionService.HideLoadingAsync();
return (cipherId, userVerified);
}
catch
{
await _deviceActionService.HideLoadingAsync();
throw;
}
}
public async Task EnsureUnlockedVaultAsync()
{
if (!await _stateService.IsAuthenticatedAsync()
||
await _vaultTimeoutService.IsLoggedOutByTimeoutAsync()
||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
await NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget.HomeLogin);
return;
}
if (!await _vaultTimeoutService.IsLockedAsync())
{
return;
}
await NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget.Lock);
}
private async Task NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget navTarget)
{
_unlockVaultTcs?.TrySetCanceled();
_unlockVaultTcs = new TaskCompletionSource<bool>();
_messagingService.Value.Send(Bit.Core.Constants.NavigateToMessageCommand, navTarget);
await _unlockVaultTcs.Task;
}
public Task InformExcludedCredentialAsync(string[] existingCipherIds)
{
// TODO: Show excluded credential to the user in some screen.
return Task.FromResult(true);
}
public void SetCheckHasVaultBeenUnlockedInThisTransaction(Func<bool> checkHasVaultBeenUnlockedInThisTransaction)
{
_checkHasVaultBeenUnlockedInThisTransaction = checkHasVaultBeenUnlockedInThisTransaction;
}
public void Confirm(string cipherId, bool? userVerified) => _confirmCredentialTcs?.TrySetResult((cipherId, userVerified));
public void ConfirmVaultUnlocked() => _unlockVaultTcs?.TrySetResult(true);
public async Task ConfirmAsync(string cipherId, bool alreadyHasFido2Credential, bool? userVerified)
{
if (alreadyHasFido2Credential
&&
!await _platformUtilsService.ShowDialogAsync(
AppResources.ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey,
AppResources.OverwritePasskey,
AppResources.Yes,
AppResources.No))
{
return;
}
Confirm(cipherId, userVerified);
}
public void Cancel() => _confirmCredentialTcs?.TrySetCanceled();
public void OnConfirmationException(Exception ex) => _confirmCredentialTcs?.TrySetException(ex);
private async Task<CancellableResult<bool>> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId)
{
try
{
if (selectedCipherId is null && userVerificationPreference == Fido2UserVerificationPreference.Discouraged)
{
return new CancellableResult<bool>(false);
}
var shouldCheckMasterPasswordReprompt = false;
if (selectedCipherId != null)
{
var encrypted = await _cipherService.GetAsync(selectedCipherId);
var cipher = await encrypted.DecryptAsync();
shouldCheckMasterPasswordReprompt = cipher?.Reprompt == Core.Enums.CipherRepromptType.Password;
}
return await _userVerificationMediatorService.VerifyUserForFido2Async(
new Fido2UserVerificationOptions(
shouldCheckMasterPasswordReprompt,
userVerificationPreference,
HasVaultBeenUnlockedInThisTransaction,
rpId)
);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return new CancellableResult<bool>(false);
}
}
public Fido2UserVerificationOptions? GetCurrentUserVerificationOptions() => _currentDefaultUserVerificationOptions;
}
}

View File

@@ -24,7 +24,6 @@ using Bit.App.Droid.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using FileProvider = AndroidX.Core.Content.FileProvider;
using Bit.Core.Utilities.Fido2;
namespace Bit.Droid
{
@@ -168,13 +167,6 @@ namespace Bit.Droid
base.OnNewIntent(intent);
try
{
if (intent?.GetStringExtra(CredentialProviderConstants.Fido2CredentialAction) == CredentialProviderConstants.Fido2CredentialCreate
&&
_appOptions != null)
{
_appOptions.HasUnlockedInThisTransaction = false;
}
if (intent?.GetStringExtra("uri") is string uri)
{
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE);
@@ -333,15 +325,12 @@ namespace Bit.Droid
private AppOptions GetOptions()
{
var fido2CredentialAction = Intent.GetStringExtra(CredentialProviderConstants.Fido2CredentialAction);
var options = new AppOptions
{
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
Fido2CredentialAction = fido2CredentialAction,
FromFido2Framework = !string.IsNullOrWhiteSpace(fido2CredentialAction),
CreateSend = GetCreateSendRequest(Intent)
};
var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);

View File

@@ -20,10 +20,7 @@ using Bit.App.Utilities;
using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls;
using Bit.App.Platforms.Android.Autofill;
using Bit.Core.Enums;
using Bit.Core.Services.UserVerification;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -88,57 +85,6 @@ namespace Bit.Droid
ServiceContainer.Resolve<IWatchDeviceService>(),
ServiceContainer.Resolve<IConditionedAwaiterManager>());
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
var userPinService = new UserPinService(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<ICryptoService>(),
ServiceContainer.Resolve<IVaultTimeoutService>());
ServiceContainer.Register<IUserPinService>(userPinService);
var userVerificationMediatorService = new UserVerificationMediatorService(
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
userPinService,
deviceActionService,
ServiceContainer.Resolve<IUserVerificationService>());
ServiceContainer.Register<IUserVerificationMediatorService>(userVerificationMediatorService);
var fido2AuthenticatorService = new Fido2AuthenticatorService(
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<ISyncService>(),
ServiceContainer.Resolve<ICryptoFunctionService>(),
userVerificationMediatorService);
ServiceContainer.Register<IFido2AuthenticatorService>(fido2AuthenticatorService);
var fido2GetAssertionUserInterface = new Fido2GetAssertionUserInterface(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>(),
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IUserVerificationMediatorService>());
ServiceContainer.Register<IFido2AndroidGetAssertionUserInterface>(fido2GetAssertionUserInterface);
var fido2MakeCredentialUserInterface = new Fido2MakeCredentialUserInterface(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>(),
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IUserVerificationMediatorService>(),
ServiceContainer.Resolve<IDeviceActionService>(),
ServiceContainer.Resolve<IPlatformUtilsService>());
ServiceContainer.Register<IFido2MakeCredentialConfirmationUserInterface>(fido2MakeCredentialUserInterface);
var fido2ClientService = new Fido2ClientService(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IEnvironmentService>(),
ServiceContainer.Resolve<ICryptoFunctionService>(),
ServiceContainer.Resolve<IFido2AuthenticatorService>(),
fido2GetAssertionUserInterface,
fido2MakeCredentialUserInterface);
ServiceContainer.Register<IFido2ClientService>(fido2ClientService);
ServiceContainer.Register<IFido2MediatorService>(new Fido2MediatorService(
fido2AuthenticatorService,
fido2ClientService,
ServiceContainer.Resolve<ICipherService>()));
}
#if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -214,6 +160,7 @@ namespace Bit.Droid
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger);
var biometricService = new BiometricService(stateService, cryptoService);
var userPinService = new UserPinService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
@@ -237,6 +184,7 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
ServiceContainer.Register<IUserPinService>(userPinService);
// Push
#if FDROID

View File

@@ -1,9 +1,8 @@
#if !FDROID
#if !FDROID
using System;
using Android.App;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Firebase.Messaging;
@@ -21,7 +20,7 @@ namespace Bit.Droid.Push
try {
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await stateService.SetPushRegisteredTokenAsync(token);
await pushNotificationService.RegisterAsync();
}
@@ -39,33 +38,13 @@ namespace Bit.Droid.Push
{
return;
}
JObject obj = null;
if (message.Data.TryGetValue("data", out var data))
{
// Legacy GCM format
obj = JObject.Parse(data);
}
else if (message.Data.TryGetValue("type", out var typeData) &&
Enum.TryParse(typeData, out NotificationType type))
{
// New FCMv1 format
obj = new JObject
{
{ "type", (int)type }
};
if (message.Data.TryGetValue("payload", out var payloadData))
{
obj.Add("payload", payloadData);
}
}
if (obj == null)
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
if (data == null)
{
return;
}
var obj = JObject.Parse(data);
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
"pushNotificationListenerService");
await listener.OnMessageAsync(obj, Device.Android);

View File

@@ -1,11 +1,11 @@
using Android.App;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Credentials;
using Android.OS;
using Android.Provider;
using Android.Views.Autofill;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
@@ -43,28 +43,9 @@ namespace Bit.Droid.Services
{
return false;
}
try
{
var activity = (MainActivity)Platform.CurrentActivity;
if (activity == null)
{
return false;
}
var credManager = activity.GetSystemService(Java.Lang.Class.FromType(typeof(CredentialManager))) as CredentialManager;
if (credManager == null)
{
return false;
}
var credentialProviderServiceComponentName = new ComponentName(activity, Java.Lang.Class.FromType(typeof(CredentialProviderService)));
return credManager.IsEnabledCredentialProviderService(credentialProviderServiceComponentName);
}
catch (Java.Lang.NullPointerException)
{
// CredentialManager API is not working fully and may return a NullPointerException even if the CredentialProviderService is working and enabled
// Info Here: https://developer.android.com/reference/android/credentials/CredentialManager#isEnabledCredentialProviderService(android.content.ComponentName)
// TODO - find a way to programmatically check if the credential provider service is enabled
return false;
}
catch
@@ -203,10 +184,7 @@ namespace Bit.Droid.Services
{
try
{
// We should try to find a way to programmatically disable the provider service when the API allows for it.
// For now we'll take the user to Credential Settings so they can manually disable it
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
deviceActionService.OpenCredentialProviderSettings();
// TODO - find a way to programmatically disable the provider service, or take the user to the settings page where they can do it
}
catch { }
}

View File

@@ -1,4 +1,6 @@
using Android.App;
using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Nfc;
@@ -15,14 +17,11 @@ using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
using Bit.App.Utilities.Prompts;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.App.Droid.Utilities;
using Bit.App.Models;
using Bit.Droid.Autofill;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Resource = Bit.Core.Resource;
using Application = Android.App.Application;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
namespace Bit.Droid.Services
{
@@ -74,28 +73,17 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName)
{
try
{
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 = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null;
}
catch (IntentSender.SendIntentException)
{
return false;
}
catch (Android.Util.AndroidException)
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 = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null;
}
public async Task ShowLoadingAsync(string text)
@@ -205,7 +193,7 @@ namespace Bit.Droid.Services
string text = null, string okButtonText = null, string cancelButtonText = null,
bool numericKeyboard = false, bool autofocus = true, bool password = false)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return Task.FromResult<string>(null);
@@ -262,7 +250,7 @@ namespace Bit.Droid.Services
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return Task.FromResult<ValidatablePromptResponse?>(null);
@@ -339,7 +327,7 @@ namespace Bit.Droid.Services
public void RateApp()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var rateIntent = RateIntentForUrl("market://details", activity);
@@ -372,14 +360,14 @@ namespace Bit.Droid.Services
public bool SupportsNfc()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var manager = activity.GetSystemService(Context.NfcService) as NfcManager;
return manager.DefaultAdapter?.IsEnabled ?? false;
}
public bool SupportsCamera()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
}
@@ -395,7 +383,7 @@ namespace Bit.Droid.Services
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return Task.FromResult<string>(null);
@@ -476,7 +464,7 @@ namespace Bit.Droid.Services
public void OpenAccessibilityOverlayPermissionSettings()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var intent = new Intent(Settings.ActionManageOverlayPermission);
@@ -505,10 +493,10 @@ namespace Bit.Droid.Services
public void OpenCredentialProviderSettings()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var pendingIntent = ICredentialManager.Create(activity).CreateSettingsPendingIntent();
var pendingIntent = CredentialManager.Create(activity).CreateSettingsPendingIntent();
pendingIntent.Send();
}
catch (ActivityNotFoundException)
@@ -528,7 +516,7 @@ namespace Bit.Droid.Services
{
try
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var intent = new Intent(Settings.ActionAccessibilitySettings);
activity.StartActivity(intent);
}
@@ -537,7 +525,7 @@ namespace Bit.Droid.Services
public void OpenAutofillSettings()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var intent = new Intent(Settings.ActionRequestSetAutofillService);
@@ -565,92 +553,10 @@ namespace Bit.Droid.Services
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
return SystemClock.ElapsedRealtime();
}
public async Task ExecuteFido2CredentialActionAsync(AppOptions appOptions)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null || string.IsNullOrWhiteSpace(appOptions.Fido2CredentialAction))
{
return;
}
if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialGet)
{
await ExecuteFido2GetCredentialAsync(appOptions);
}
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialCreate)
{
await ExecuteFido2CreateCredentialAsync();
}
// Clear CredentialAction and FromFido2Framework values to avoid erratic behaviors in subsequent navigation/flows
// For Fido2CredentialGet these are no longer needed as a new Activity will be initiated.
// For Fido2CredentialCreate the app will rely on IFido2MakeCredentialConfirmationUserInterface.IsConfirmingNewCredential
appOptions.Fido2CredentialAction = null;
appOptions.FromFido2Framework = false;
}
private async Task ExecuteFido2GetCredentialAsync(AppOptions appOptions)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return;
}
try
{
var request = AndroidX.Credentials.Provider.PendingIntentHandler.RetrieveBeginGetCredentialRequest(activity.Intent);
var response = new AndroidX.Credentials.Provider.BeginGetCredentialResponse();;
var credentialEntries = new List<AndroidX.Credentials.Provider.CredentialEntry>();
foreach (var option in request.BeginGetCredentialOptions.OfType<AndroidX.Credentials.Provider.BeginGetPublicKeyCredentialOption>())
{
credentialEntries.AddRange(await Bit.App.Platforms.Android.Autofill.CredentialHelpers.PopulatePasskeyDataAsync(request.CallingAppInfo, option, activity, appOptions.HasUnlockedInThisTransaction));
}
if (credentialEntries.Any())
{
response = new AndroidX.Credentials.Provider.BeginGetCredentialResponse.Builder()
.SetCredentialEntries(credentialEntries)
.Build();
}
var result = new Android.Content.Intent();
AndroidX.Credentials.Provider.PendingIntentHandler.SetBeginGetCredentialResponse(result, response);
activity.SetResult(Result.Ok, result);
activity.Finish();
}
catch (Exception ex)
{
Bit.Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
activity.SetResult(Result.Canceled);
activity.Finish();
}
}
private async Task ExecuteFido2CreateCredentialAsync()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null) { return; }
try
{
var getRequest = AndroidX.Credentials.Provider.PendingIntentHandler.RetrieveProviderCreateCredentialRequest(activity.Intent);
await Bit.App.Platforms.Android.Autofill.CredentialHelpers.CreateCipherPasskeyAsync(getRequest, activity);
}
catch (Exception ex)
{
Bit.Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
activity.SetResult(Result.Canceled);
activity.Finish();
}
}
public void CloseMainApp()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return;
@@ -691,7 +597,7 @@ namespace Bit.Droid.Services
public float GetSystemFontSizeScale()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity as MainActivity;
return activity?.Resources?.Configuration?.FontScale ?? 1;
}

View File

@@ -8,7 +8,6 @@ using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Accessibility;
using Java.Lang;
using Bit.App.Droid.Utilities;
namespace Bit.Droid.Tile
{
@@ -77,7 +76,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(AccessibilityActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("autofillTileClicked", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: true);
StartActivityAndCollapse(intent);
}
private void ShowConfigErrorDialog()

View File

@@ -1,8 +1,15 @@
using Android.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Service.QuickSettings;
using Bit.App.Droid.Utilities;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace Bit.Droid.Tile
@@ -55,7 +62,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("generatorTile", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
StartActivityAndCollapse(intent);
}
}
}

View File

@@ -1,8 +1,15 @@
using Android.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Service.QuickSettings;
using Bit.App.Droid.Utilities;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace Bit.Droid.Tile
@@ -56,7 +63,7 @@ namespace Bit.Droid.Tile
var intent = new Intent(this, typeof(MainActivity));
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
intent.PutExtra("myVaultTile", true);
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
StartActivityAndCollapse(intent);
}
}
}

View File

@@ -2,7 +2,6 @@
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Service.QuickSettings;
using Bit.App.Utilities;
namespace Bit.App.Droid.Utilities
@@ -65,26 +64,5 @@ namespace Bit.App.Droid.Utilities
return pendingIntentFlags;
}
public static void StartActivityAndCollapseWithIntent(this TileService service, Intent intent, bool isMutable)
{
//For Android 14+ We need to use PendingIntent instead of Intent directly. Older versions still need to use Intent.
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
{
service.StartActivityAndCollapse(intent);
return;
}
var pendingIntent = PendingIntent.GetActivity(
service.ApplicationContext,
0,
intent,
AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, isMutable)
);
if (pendingIntent == null)
{
return;
}
service.StartActivityAndCollapse(pendingIntent);
}
}
}

View File

@@ -1,37 +0,0 @@
using Android.OS;
using AndroidX.Credentials.Provider;
using Bit.Core.Utilities;
using Java.Security;
namespace Bit.App.Droid.Utilities
{
public static class CallingAppInfoExtensions
{
public static string GetAndroidOrigin(this CallingAppInfo callingAppInfo)
{
if (Build.VERSION.SdkInt < BuildVersionCodes.P || callingAppInfo?.SigningInfo?.GetApkContentsSigners().Any() != true)
{
return null;
}
var cert = callingAppInfo.SigningInfo.GetApkContentsSigners()[0].ToByteArray();
var md = MessageDigest.GetInstance("SHA-256");
var certHash = md.Digest(cert);
return $"android:apk-key-hash:{CoreHelpers.Base64UrlEncode(certHash)}";
}
public static string GetLatestCertificationFingerprint(this CallingAppInfo callingAppInfo)
{
if (callingAppInfo.SigningInfo.HasMultipleSigners)
{
return null;
}
var signature = callingAppInfo.SigningInfo.GetSigningCertificateHistory()[0].ToByteArray();
var md = MessageDigest.GetInstance("SHA-256");
var digestedSignature = md.Digest(signature);
var normalizedFingerprint = string.Join(":", digestedSignature.Select(b => b.ToString("X2")));
return normalizedFingerprint;
}
}
}

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key>
<string>2024.10.111</string>
<string>2024.2.2</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconName</key>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,481 +0,0 @@
{
"apps": [
{
"type": "android",
"info": {
"package_name": "com.android.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "DA:63:3D:34:B6:9E:63:AE:21:03:B4:9D:53:CE:05:2F:C5:F7:F3:C5:3A:AB:94:FD:C2:A2:08:BD:FD:14:24:9C"
},
{
"build": "release",
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.dev",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "90:44:EE:5F:EE:4B:BC:5E:21:DD:44:66:54:31:C4:EB:1F:1F:71:A3:27:16:A0:BC:92:7B:CB:B3:92:33:CA:BF"
},
{
"build": "release",
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "20:19:DF:A1:FB:23:EF:BF:70:C5:BC:D1:44:3C:5B:EA:B0:4F:3F:2F:F4:36:6E:9A:C1:E3:45:76:39:A2:4C:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.chromium.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.google.android.apps.chrome",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.fennec_webauthndebug",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.firefox",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.firefox_beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.focus",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.fennec_aurora",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "BC:04:88:83:8D:06:F4:CA:6B:F3:23:86:DA:AB:0D:D8:EB:CF:3E:77:30:78:74:59:F6:2F:B3:CD:14:A1:BA:AA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.rocket",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "86:3A:46:F0:97:39:32:B7:D0:19:9B:54:91:12:74:1C:2D:27:31:AC:72:EA:11:B7:52:3A:A9:0A:11:BF:56:91"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.dev",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.rolling",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.local",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.brave.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.brave.browser_beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.brave.browser_nightly",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "app.vanadium.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.vivaldi.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.vivaldi.browser.snapshot",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.vivaldi.browser.sopranos",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.citrix.Receiver",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "3D:D1:12:67:10:69:AB:36:4E:F9:BE:73:9A:B7:B5:EE:15:E1:CD:E9:D8:75:7B:1B:F0:64:F5:0C:55:68:9A:49"
},
{
"build": "release",
"cert_fingerprint_sha256": "CE:B2:23:D7:77:09:F2:B6:BC:0B:3A:78:36:F5:A5:AF:4C:E1:D3:55:F4:A7:28:86:F7:9D:F8:0D:C9:D6:12:2E"
},
{
"build": "release",
"cert_fingerprint_sha256": "AA:D0:D4:57:E6:33:C3:78:25:77:30:5B:C1:B2:D9:E3:81:41:C7:21:DF:0D:AA:6E:29:07:2F:C4:1D:34:F0:AB"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.android.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C9:00:9D:01:EB:F9:F5:D0:30:2B:C7:1B:2F:E9:AA:9A:47:A4:32:BB:A1:73:08:A3:11:1B:75:D7:B2:14:90:25"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.sec.android.app.sbrowser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
},
{
"build": "release",
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.sec.android.app.sbrowser.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
},
{
"build": "release",
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.google.android.gms",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "7C:E8:3C:1B:71:F3:D5:72:FE:D0:4C:8D:40:C5:CB:10:FF:75:E6:D8:7D:9D:F6:FB:D5:3F:04:68:C2:90:50:53"
},
{
"build": "release",
"cert_fingerprint_sha256": "D2:2C:C5:00:29:9F:B2:28:73:A0:1A:01:0D:E1:C8:2F:BE:4D:06:11:19:B9:48:14:DD:30:1D:AB:50:CB:76:78"
},
{
"build": "release",
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
},
{
"build": "release",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.alpha",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.corp",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.broteam",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
}
]
}
}
]
}

View File

@@ -1,4 +1,9 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
@@ -94,6 +99,5 @@ namespace Bit.Core.Abstractions
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
Task<ConfigResponse> GetConfigsAsync();
Task<string> GetFastmailAccountIdAsync(string apiKey);
Task<List<Utilities.DigitalAssetLinks.Statement>> GetDigitalAssetLinksForRpAsync(string rpId);
}
}

View File

@@ -1,7 +0,0 @@
namespace Bit.Core.Services
{
public interface IAssetLinksService
{
Task<bool> ValidateAssetLinksAsync(string rpId, string packageName, string normalizedFingerprint);
}
}

View File

@@ -1,4 +1,7 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
@@ -34,7 +37,5 @@ namespace Bit.Core.Abstractions
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
Task SoftDeleteWithServerAsync(string id);
Task RestoreWithServerAsync(string id);
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
Task CopyTotpCodeIfNeededAsync(CipherView cipher);
}
}

View File

@@ -1,10 +1,11 @@
namespace Bit.Core.Abstractions
using System;
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public enum AwaiterPrecondition
{
EnvironmentUrlsInited,
AndroidWindowCreated,
AutofillIOSExtensionViewDidAppear
EnvironmentUrlsInited
}
public interface IConditionedAwaiterManager
@@ -12,6 +13,5 @@
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
void Recreate(AwaiterPrecondition awaiterPrecondition);
}
}

View File

@@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities.Prompts;
using Bit.Core.Enums;
using Bit.Core.Models;
@@ -41,7 +40,6 @@ namespace Bit.App.Abstractions
void OpenCredentialProviderSettings();
void OpenAutofillSettings();
long GetActiveTime();
Task ExecuteFido2CredentialActionAsync(AppOptions appOptions);
void CloseMainApp();
float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync();

View File

@@ -0,0 +1,9 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2AuthenticationService
{
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams);
}
}

View File

@@ -4,8 +4,9 @@ namespace Bit.Core.Abstractions
{
public interface IFido2AuthenticatorService
{
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
void Init(IFido2UserInterface userInterface);
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams);
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams);
// TODO: Should this return a List? Or maybe IEnumerable?
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
}

View File

@@ -20,9 +20,8 @@ namespace Bit.Core.Abstractions
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-createCredential
/// </summary>
/// <param name="createCredentialParams">The parameters for the credential creation operation</param>
/// <param name="extraParams">Extra parameters for the credential creation operation</param>
/// <returns>The new credential</returns>
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams, Fido2ExtraCreateCredentialParams extraParams);
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
/// <summary>
/// Allows WebAuthn Relying Party scripts to discover and use an existing public key credential, with the users consent.
@@ -30,8 +29,7 @@ namespace Bit.Core.Abstractions
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-getAssertion
/// </summary>
/// <param name="assertCredentialParams">The parameters for the credential assertion operation</param>
/// <param name="extraParams">Extra parameters for the credential assertion operation</param>
/// <returns>The asserted credential</returns>
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams, Fido2ExtraAssertCredentialParams extraParams);
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
}
}

View File

@@ -1,20 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public struct Fido2GetAssertionUserInterfaceCredential
{
public string CipherId { get; set; }
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
}
public interface IFido2GetAssertionUserInterface : IFido2UserInterface
{
/// <summary>
/// Ask the user to pick a credential from a list of existing credentials.
/// </summary>
/// <param name="credentials">The credentials that the user can pick from, and if the user must be verified before completing the operation</param>
/// <returns>The ID of the cipher that contains the credentials the user picked, and if the user was verified before completing the operation</returns>
Task<(string CipherId, bool UserVerified)> PickCredentialAsync(Fido2GetAssertionUserInterfaceCredential[] credentials);
}
}

View File

@@ -1,66 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2MakeCredentialConfirmationUserInterface : IFido2MakeCredentialUserInterface
{
/// <summary>
/// Call this method after the user chose where to save the new Fido2 credential.
/// </summary>
/// <param name="cipherId">
/// Cipher ID where to save the new credential.
/// If <c>null</c> a new default passkey cipher item will be created
/// </param>
/// <param name="userVerified">
/// Whether the user has been verified or not.
/// If <c>null</c> verification has not taken place yet.
/// </param>
void Confirm(string cipherId, bool? userVerified);
/// <summary>
/// Call this method after the user chose where to save the new Fido2 credential.
/// </summary>
/// <param name="cipherId">
/// Cipher ID where to save the new credential.
/// If <c>null</c> a new default passkey cipher item will be created
/// </param>
/// <param name="alreadyHasFido2Credential">
/// If the cipher corresponding to the <paramref name="cipherId"/> already has a Fido2 credential.
/// </param>
/// <param name="userVerified">
/// Whether the user has been verified or not.
/// If <c>null</c> verification has not taken place yet.
/// </param>
Task ConfirmAsync(string cipherId, bool alreadyHasFido2Credential, bool? userVerified);
/// <summary>
/// Cancels the current flow to make a credential
/// </summary>
void Cancel();
/// <summary>
/// Call this if an exception needs to happen on the credential making process
/// </summary>
void OnConfirmationException(Exception ex);
/// <summary>
/// True if we are already confirming a new credential.
/// </summary>
bool IsConfirmingNewCredential { get; }
/// <summary>
/// Call this after the vault was unlocked so that Fido2 credential creation can proceed.
/// </summary>
void ConfirmVaultUnlocked();
/// <summary>
/// True if we are waiting for the vault to be unlocked.
/// </summary>
bool IsWaitingUnlockVault { get; }
Fido2UserVerificationOptions? GetCurrentUserVerificationOptions();
void SetCheckHasVaultBeenUnlockedInThisTransaction(Func<bool> checkHasVaultBeenUnlockedInThisTransaction);
}
}

View File

@@ -1,44 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public struct Fido2ConfirmNewCredentialParams
{
///<summary>
/// The name of the credential.
///</summary>
public string CredentialName { get; set; }
///<summary>
/// The name of the user.
///</summary>
public string UserName { get; set; }
/// <summary>
/// The preference to whether or not the user must be verified before completing the operation.
/// </summary>
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
/// <summary>
/// The relying party identifier
/// </summary>
public string RpId { get; set; }
}
public interface IFido2MakeCredentialUserInterface : IFido2UserInterface
{
/// <summary>
/// Inform the user that the operation was cancelled because their vault contains excluded credentials.
/// </summary>
/// <param name="existingCipherIds">The IDs of the excluded credentials.</param>
/// <returns>When user has confirmed the message</returns>
Task InformExcludedCredentialAsync(string[] existingCipherIds);
/// <summary>
/// Ask the user to confirm the creation of a new credential.
/// </summary>
/// <param name="confirmNewCredentialParams">The parameters to use when asking the user to confirm the creation of a new credential.</param>
/// <returns>The ID of the cipher where the new credential should be saved, and if the user was verified before completing the operation</returns>
Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams);
}
}

View File

@@ -1,14 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2MediatorService
{
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams, Fido2ExtraCreateCredentialParams extraParams);
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams, Fido2ExtraAssertCredentialParams extraParams);
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
}
}

View File

@@ -1,11 +1,94 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
/// <summary>
/// Parameters used to ask the user to pick a credential from a list of existing credentials.
/// </summary>
public struct Fido2PickCredentialParams
{
/// <summary>
/// The IDs of the credentials that the user can pick from.
/// </summary>
public string[] CipherIds { get; set; }
/// <summary>
/// Whether or not the user must be verified before completing the operation.
/// </summary>
public bool UserVerification { get; set; }
}
/// <summary>
/// The result of asking the user to pick a credential from a list of existing credentials.
/// </summary>
public struct Fido2PickCredentialResult
{
/// <summary>
/// The ID of the cipher that contains the credentials the user picked.
/// </summary>
public string CipherId { get; set; }
/// <summary>
/// Whether or not the user was verified before completing the operation.
/// </summary>
public bool UserVerified { get; set; }
}
public struct Fido2ConfirmNewCredentialParams
{
///<summary>
/// The name of the credential.
///</summary>
public string CredentialName { get; set; }
///<summary>
/// The name of the user.
///</summary>
public string UserName { get; set; }
/// <summary>
/// Whether or not the user must be verified before completing the operation.
/// </summary>
public bool UserVerification { get; set; }
public string RpId { get; set; }
}
public struct Fido2ConfirmNewCredentialResult
{
/// <summary>
/// The name of the user.
/// </summary>
public string CipherId { get; set; }
/// <summary>
/// Whether or not the user was verified.
/// </summary>
public bool UserVerified { get; set; }
}
public interface IFido2UserInterface
{
/// <summary>
/// Whether the vault has been unlocked during this transaction
/// Ask the user to pick a credential from a list of existing credentials.
/// </summary>
bool HasVaultBeenUnlockedInThisTransaction { get; }
/// <param name="pickCredentialParams">The parameters to use when asking the user to pick a credential.</param>
/// <returns>The ID of the cipher that contains the credentials the user picked.</returns>
Task<Fido2PickCredentialResult> PickCredentialAsync(Fido2PickCredentialParams pickCredentialParams);
/// <summary>
/// Inform the user that the operation was cancelled because their vault contains excluded credentials.
/// </summary>
/// <param name="existingCipherIds">The IDs of the excluded credentials.</param>
/// <returns>When user has confirmed the message</returns>
Task InformExcludedCredential(string[] existingCipherIds);
/// <summary>
/// Ask the user to confirm the creation of a new credential.
/// </summary>
/// <param name="confirmNewCredentialParams">The parameters to use when asking the user to confirm the creation of a new credential.</param>
/// <returns>The ID of the cipher where the new credential should be saved.</returns>
Task<Fido2ConfirmNewCredentialResult> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams);
/// <summary>
/// Make sure that the vault is unlocked.

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
@@ -9,7 +10,5 @@ namespace Bit.App.Abstractions
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
Task<bool> ShouldByPassMasterPasswordRepromptAsync();
}
}

View File

@@ -1,4 +1,7 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.Core.Abstractions
{
@@ -26,7 +29,7 @@ namespace Bit.Core.Abstractions
bool SupportsDuo();
Task<bool> SupportsBiometricAsync();
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false);
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false);
long GetActiveTime();
}
}

View File

@@ -186,7 +186,6 @@ namespace Bit.Core.Abstractions
Task<BwRegion?> GetActiveUserRegionAsync();
Task<BwRegion?> GetPreAuthRegionAsync();
Task SetPreAuthRegionAsync(BwRegion value);
Task ReloadStateAsync();
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetPinProtectedAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]

View File

@@ -1,12 +1,9 @@
using Bit.Core.Services;
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public interface IUserPinService
{
Task<bool> IsPinLockEnabledAsync();
Task SetupPinAsync(string pin, bool requireMasterPasswordOnRestart);
Task<bool> VerifyPinAsync(string inputPin);
Task<bool> VerifyPinAsync(string inputPin, string email, KdfConfig kdfConfig, PinLockType pinLockType);
}
}

View File

@@ -1,28 +0,0 @@
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IUserVerificationMediatorService
{
Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options);
Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options);
Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options);
Task<CancellableResult<UVResult>> PerformOSUnlockAsync();
Task<CancellableResult<UVResult>> VerifyPinCodeAsync();
Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt);
public struct UVResult
{
public UVResult(bool canPerform, bool isVerified)
{
CanPerform = canPerform;
IsVerified = isVerified;
}
public bool CanPerform { get; set; }
public bool IsVerified { get; set; }
}
}
}

View File

@@ -1,11 +1,11 @@
using Bit.Core.Enums;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.Core.Abstractions
{
public interface IUserVerificationService
{
Task<bool> VerifyUser(string secret, VerificationType verificationType);
Task<bool> VerifyMasterPasswordAsync(string masterPassword);
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
}
}

View File

@@ -9,12 +9,10 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Pages;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
@@ -38,9 +36,6 @@ namespace Bit.App
private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService;
private readonly ILogger _logger;
#if ANDROID
private LazyResolve<IFido2MakeCredentialConfirmationUserInterface> _fido2MakeCredentialConfirmationUserInterface = new LazyResolve<IFido2MakeCredentialConfirmationUserInterface>();
#endif
private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
@@ -51,6 +46,7 @@ namespace Bit.App
// This queue keeps those actions so that when the app has resumed they can still be executed.
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
private readonly Queue<Action> _onResumeActions = new Queue<Action>();
private bool _hasNavigatedToAutofillWindow;
#if ANDROID
@@ -108,10 +104,7 @@ namespace Bit.App
Options.MyVaultTile = appOptions.MyVaultTile;
Options.GeneratorTile = appOptions.GeneratorTile;
Options.FromAutofillFramework = appOptions.FromAutofillFramework;
Options.FromFido2Framework = appOptions.FromFido2Framework;
Options.Fido2CredentialAction = appOptions.Fido2CredentialAction;
Options.CreateSend = appOptions.CreateSend;
Options.HasUnlockedInThisTransaction = appOptions.HasUnlockedInThisTransaction;
}
}
@@ -127,17 +120,41 @@ namespace Bit.App
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
}
//When executing from CredentialProviderSelectionActivity we don't have "Options" so we need to filter "manually"
//In the CredentialProviderSelectionActivity we don't need to show any Page, so we just create a "dummy" Window with a NavigationPage to avoid crashing.
if (activationState != null
&& activationState.State.ContainsKey("CREDENTIAL_DATA")
&& activationState.State.ContainsKey("credentialProviderCipherId"))
//"Internal" Autofill and Uri/Otp/CreateSend. This is where we create the autofill specific Window
if (Options != null && (Options.FromAutofillFramework || Options.Uri != null || Options.OtpData != null || Options.CreateSend != null))
{
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
_isResumed = true; //Specifically for the Autofill scenario we need to manually set the _isResumed here
_hasNavigatedToAutofillWindow = true;
return new AutoFillWindow(new NavigationPage(new AndroidNavigationRedirectPage()));
}
_isResumed = true;
return new ResumeWindow(new NavigationPage(new AndroidNavigationRedirectPage(Options)));
var homePage = new HomePage(Options);
// WORKAROUND: If the user autofills with Accessibility Services enabled and goes back to the application then there is currently an issue
// where this method is called again
// thus it goes through here and the user goes to HomePage as we see here.
// So to solve this, the next flag check has been added which then turns on a flag on the home page
// that will trigger a navigation on the accounts manager when it loads; workarounding this behavior and navigating the user
// to the proper page depending on its state.
// WARNING: this doens't navigate the user to where they were but it acts as if the user had changed their account.
if(_hasNavigatedToAutofillWindow)
{
homePage.PerformNavigationOnAccountChangedOnLoad = true;
// this is needed because when coming back from AutofillWindow OnResume won't be called and we need this flag
// so that void Navigate(NavigationTarget navTarget, INavigationParams navParams) doesn't enqueue the navigation
// and it performs it directly.
_isResumed = true;
_hasNavigatedToAutofillWindow = false;
}
//If we have an existing MainAppWindow we can use that one
var mainAppWindow = Windows.OfType<MainAppWindow>().FirstOrDefault();
if (mainAppWindow != null)
{
mainAppWindow.PendingPage = new NavigationPage(homePage);
}
//Create new main window
return new MainAppWindow(new NavigationPage(homePage));
}
#else
//iOS doesn't use the CreateWindow override used in Android so we just set the Application.Current.MainPage directly
@@ -154,7 +171,7 @@ namespace Bit.App
Application.Current.MainPage = value;
}
}
}
}
#endif
public App() : this(null)
@@ -184,182 +201,132 @@ namespace Bit.App
_accountsManager.Init(() => Options, this);
_broadcasterService.Subscribe(nameof(App), BroadcastServiceMessageCallbackAsync);
Bootstrap();
}
private async void BroadcastServiceMessageCallbackAsync(Message message)
{
try
{
ArgumentNullException.ThrowIfNull(message);
if (message.Command == "showDialog")
_broadcasterService.Subscribe(nameof(App), async (message) =>
{
try
{
var details = message.Data as DialogDetails;
ArgumentNullException.ThrowIfNull(details);
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
await MainThread.InvokeOnMainThreadAsync(ShowDialogAction);
async Task ShowDialogAction()
if (message.Command == "showDialog")
{
if (!string.IsNullOrWhiteSpace(details.CancelText))
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
await MainThread.InvokeOnMainThreadAsync(async () =>
{
ArgumentNullException.ThrowIfNull(MainPage);
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await _deviceActionService.DisplayAlertAsync(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
}
#if IOS
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
ResumedAsync().FireAndForget();
}
else if (message.Command == "slept")
{
await SleptAsync();
}
#endif
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
Options.OtpData = new OtpData((string)message.Data);
ResumedAsync().FireAndForget();
}
await MainThread.InvokeOnMainThreadAsync(ExecuteNavigationAction);
async Task ExecuteNavigationAction()
else if (message.Command == "slept")
{
if (MainPage is TabsPage tabsPage)
await SleptAsync();
}
#endif
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
ArgumentNullException.ThrowIfNull(tabsPage.Navigation.ModalStack);
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
Options.OtpData = new OtpData((string)message.Data);
}
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
await MainThread.InvokeOnMainThreadAsync(async () =>
{
if (MainPage is TabsPage tabsPage)
{
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
{
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
else if (message.Command == Constants.ForceUpdatePassword)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
});
}
else if (message.Command == Constants.ForceSetPassword)
{
await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
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 == Constants.CredentialNavigateToAutofillCipherMessageCommand && message.Data is Fido2ConfirmNewCredentialParams createParams)
catch (Exception ex)
{
ArgumentNullException.ThrowIfNull(MainPage);
ArgumentNullException.ThrowIfNull(Options);
await MainThread.InvokeOnMainThreadAsync(NavigateToCipherSelectionPageAction);
void NavigateToCipherSelectionPageAction()
{
Options.Uri = createParams.RpId;
Options.SaveUsername = createParams.UserName;
Options.SaveName = createParams.CredentialName;
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
else if (message.Command == "convertAccountToKeyConnector")
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToRemoveMasterPasswordPageAction);
async Task NavigateToRemoveMasterPasswordPageAction()
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
}
}
else if (message.Command == Constants.ForceUpdatePassword)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToUpdateTempPasswordPageAction);
async Task NavigateToUpdateTempPasswordPageAction()
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
}
}
else if (message.Command == Constants.ForceSetPassword)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToSetPasswordPageAction);
void NavigateToSetPasswordPageAction()
{
MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)));
}
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
#if ANDROID
if (message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED && _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
_fido2MakeCredentialConfirmationUserInterface.Value.OnConfirmationException(new AccountSwitchedException());
}
#endif
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
}
else if (message.Command == Constants.NavigateToMessageCommand && message.Data is NavigationTarget navigationTarget)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
Navigate(navigationTarget, null);
});
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
}
private async Task CheckPasswordlessLoginRequestsAsync()
@@ -374,6 +341,7 @@ namespace Bit.App
{
return;
}
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
if (notification == null)
{
@@ -725,15 +693,6 @@ namespace Bit.App
// If we are in background we add the Navigation Actions to a queue to execute when the app resumes.
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
#if ANDROID
if (_fido2MakeCredentialConfirmationUserInterface != null && _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
// if it's creating passkey
// and we have an active pending TaskCompletionSource
// then we let the Fido2 Authenticator flow manage the navigation to avoid issues
// like duplicated navigation to lock page.
return;
}
if (!_isResumed)
{
_onResumeActions.Enqueue(() => NavigateImpl(navTarget, navParams));

View File

@@ -45,10 +45,8 @@ namespace Bit.Core
public const string PasswordlessLoginRequestKey = "passwordlessLoginRequest";
public const string PreLoginEmailKey = "preLoginEmailKey";
public const string ConfigsKey = "configsKey";
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
public const string RegionEnvironment = "regionEnvironment";
public const string DuoCallback = "bitwarden://duo-callback";
public const string NavigateToMessageCommand = "navigateTo";
public const string CredentialNavigateToAutofillCipherMessageCommand = "credentialNavigateToAutofillCipher";
/// <summary>
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in

View File

@@ -53,13 +53,12 @@ namespace Bit.App.Controls
if (BindingContext is CipherItemViewModel cipherItemVM)
{
cipherItemVM.IconImageSuccesfullyLoaded = true;
MainThread.BeginInvokeOnMainThread(() =>
{
Icon.IsVisible = cipherItemVM.ShowIconImage;
IconPlaceholder.IsVisible = !cipherItemVM.ShowIconImage;
});
}
MainThread.BeginInvokeOnMainThread(() =>
{
Icon.IsVisible = true;
IconPlaceholder.IsVisible = false;
});
}
public void Icon_Error(object sender, FFImageLoading.Maui.CachedImageEvents.ErrorEventArgs e)

View File

@@ -50,7 +50,7 @@
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding ., Converter={StaticResource iconGlyphConverter}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<controls:BaseSettingItemView
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:core="clr-namespace:Bit.Core"
x:Class="Bit.App.Controls.ExternalLinkSubtitleItemView"
x:Name="_contentView"
ControlTemplate="{StaticResource SettingControlTemplate}">
<controls:BaseSettingItemView.GestureRecognizers>
<TapGestureRecognizer Tapped="ContentView_Tapped" />
</controls:BaseSettingItemView.GestureRecognizers>
<controls:IconLabel
Text="{Binding Source={x:Static core:BitwardenIcons.ExternalLink}}"
TextColor="{DynamicResource TextColor}"
FontSize="25"
Margin="6,0,7,0"
HorizontalOptions="End"
VerticalOptions="Center"
SemanticProperties.Description="{Binding Title, Mode=OneWay, Source={x:Reference _contentView}}" />
</controls:BaseSettingItemView>

View File

@@ -1,26 +0,0 @@
using System.Windows.Input;
namespace Bit.App.Controls
{
public partial class ExternalLinkSubtitleItemView : BaseSettingItemView
{
public static readonly BindableProperty GoToLinkCommandProperty = BindableProperty.Create(
nameof(GoToLinkCommand), typeof(ICommand), typeof(ExternalLinkSubtitleItemView));
public ExternalLinkSubtitleItemView()
{
InitializeComponent();
}
public ICommand GoToLinkCommand
{
get => GetValue(GoToLinkCommandProperty) as ICommand;
set => SetValue(GoToLinkCommandProperty, value);
}
void ContentView_Tapped(System.Object sender, System.EventArgs e)
{
GoToLinkCommand?.Execute(null);
}
}
}

View File

@@ -80,11 +80,11 @@
<Folder Include="Utilities\Fido2\" />
<Folder Include="Controls\Picker\" />
<Folder Include="Controls\Avatar\" />
<Folder Include="Services\UserVerification\" />
<Folder Include="Utilities\WebAuthenticatorMAUI\" />
<Folder Include="Resources\Images\" />
</ItemGroup>
<ItemGroup>
<MauiImage Include="Resources\Images\dotnet_bot.svg">
<BaseSize>168,208</BaseSize>
</MauiImage>
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
<MauiFont Include="Resources\Fonts\*" />
</ItemGroup>
@@ -93,9 +93,6 @@
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<Compile Update="Controls\Settings\ExternalLinkSubtitleItemView.xaml.cs">
<DependentUpon>ExternalLinkSubtitleItemView.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\AndroidNavigationRedirectPage.xaml.cs">
<DependentUpon>AndroidNavigationRedirectPage.xaml</DependentUpon>
</Compile>
@@ -106,9 +103,6 @@
</Compile>
</ItemGroup>
<ItemGroup>
<MauiXaml Update="Controls\Settings\ExternalLinkSubtitleItemView.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Pages\AndroidNavigationRedirectPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
@@ -117,14 +111,5 @@
<None Remove="Utilities\Fido2\" />
<None Remove="Controls\Picker\" />
<None Remove="Controls\Avatar\" />
<None Remove="Services\UserVerification\" />
<None Remove="Utilities\WebAuthenticatorMAUI\" />
<None Remove="Resources\Images\" />
<None Remove="Resources\Images\empty_items_state_dark.svg" />
<None Remove="Resources\Images\empty_items_state.svg" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<MauiImage Include="Resources\Images\empty_items_state.svg" />
<MauiImage Include="Resources\Images\empty_items_state_dark.svg" />
</ItemGroup>
</Project>

View File

@@ -1,10 +0,0 @@
namespace Bit.Core.Exceptions
{
public class ValidationException : Exception
{
public ValidationException(string localizedMessage)
: base(localizedMessage)
{
}
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.App.Models
@@ -8,8 +9,6 @@ namespace Bit.App.Models
public bool MyVaultTile { get; set; }
public bool GeneratorTile { get; set; }
public bool FromAutofillFramework { get; set; }
public bool FromFido2Framework { get; set; }
public string Fido2CredentialAction { get; set; }
public CipherType? FillType { get; set; }
public string Uri { get; set; }
public CipherType? SaveType { get; set; }
@@ -26,7 +25,6 @@ namespace Bit.App.Models
public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; }
public OtpData? OtpData { get; set; }
public bool HasUnlockedInThisTransaction { get; set; }
public void SetAllFrom(AppOptions o)
{
@@ -37,7 +35,6 @@ namespace Bit.App.Models
MyVaultTile = o.MyVaultTile;
GeneratorTile = o.GeneratorTile;
FromAutofillFramework = o.FromAutofillFramework;
Fido2CredentialAction = o.Fido2CredentialAction;
FillType = o.FillType;
Uri = o.Uri;
SaveType = o.SaveType;
@@ -54,7 +51,6 @@ namespace Bit.App.Models
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
HideAccountSwitcher = o.HideAccountSwitcher;
OtpData = o.OtpData;
HasUnlockedInThisTransaction = o.HasUnlockedInThisTransaction;
}
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Domain
{
@@ -8,7 +9,7 @@ namespace Bit.Core.Models.Domain
{
if (key == null)
{
throw new ArgumentKeyNullException(nameof(key));
throw new Exception("Must provide key.");
}
if (encType == null)
@@ -23,7 +24,7 @@ namespace Bit.Core.Models.Domain
}
else
{
throw new InvalidKeyOperationException("Unable to determine encType.");
throw new Exception("Unable to determine encType.");
}
}
@@ -47,7 +48,7 @@ namespace Bit.Core.Models.Domain
}
else
{
throw new InvalidKeyOperationException("Unsupported encType/key length.");
throw new Exception("Unsupported encType/key length.");
}
if (Key != null)
@@ -71,32 +72,6 @@ namespace Bit.Core.Models.Domain
public string KeyB64 { get; set; }
public string EncKeyB64 { get; set; }
public string MacKeyB64 { get; set; }
public class ArgumentKeyNullException : ArgumentNullException
{
public ArgumentKeyNullException(string paramName) : base(paramName)
{
}
public ArgumentKeyNullException(string message, Exception innerException) : base(message, innerException)
{
}
public ArgumentKeyNullException(string paramName, string message) : base(paramName, message)
{
}
}
public class InvalidKeyOperationException : InvalidOperationException
{
public InvalidKeyOperationException(string message) : base(message)
{
}
public InvalidKeyOperationException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
public class UserKey : SymmetricCryptoKey

View File

@@ -1,7 +1,5 @@
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
namespace Bit.Core.Models.View
{
@@ -51,7 +49,7 @@ namespace Bit.Core.Models.View
public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public CipherKey Key { get; set; }
public ItemView Item
{
get
@@ -121,14 +119,5 @@ namespace Bit.Core.Models.View
public bool IsClonable => OrganizationId is null;
public bool HasFido2Credential => Type == CipherType.Login && Login?.HasFido2Credentials == true;
public string GetMainFido2CredentialUsername()
{
return Login?.MainFido2Credential?.UserName
.FallbackOnNullOrWhiteSpace(Login?.MainFido2Credential?.UserDisplayName)
.FallbackOnNullOrWhiteSpace(Login?.Username)
.FallbackOnNullOrWhiteSpace(Name)
.FallbackOnNullOrWhiteSpace(AppResources.UnknownAccount);
}
}
}

View File

@@ -51,14 +51,12 @@ namespace Bit.Core.Models.View
[JsonIgnore]
public bool DiscoverableValue {
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
set => Discoverable = value.ToString().ToLower();
set => Discoverable = value.ToString().ToLower(); // must be lowercase so it can be parsed in the current version of clients
}
[JsonIgnore]
public override string SubTitle => UserName;
public override List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions => new List<KeyValuePair<string, LinkedIdType>>();
[JsonIgnore]
public bool CanLaunch => !string.IsNullOrEmpty(RpId);
[JsonIgnore]

View File

@@ -1,4 +1,7 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
namespace Bit.Core.Models.View

View File

@@ -21,6 +21,10 @@
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="MAUI APP"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n SelfHostedEnvironment, Header=True}"
StyleClass="box-header, box-header-platform" />

View File

@@ -1,7 +1,6 @@
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Microsoft.Maui.Platform;
namespace Bit.App.Pages
{
@@ -27,7 +26,7 @@ namespace Bit.App.Pages
_apiEntry.ReturnCommand = new Command(() => _identityEntry.Focus());
_identityEntry.ReturnType = ReturnType.Next;
_identityEntry.ReturnCommand = new Command(() => _iconsEntry.Focus());
_vm.SubmitSuccessTask = () => MainThread.InvokeOnMainThreadAsync(SubmitSuccessAsync);
_vm.SubmitSuccessAction = () => MainThread.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
_vm.CloseAction = async () =>
{
await Navigation.PopModalAsync();
@@ -38,12 +37,6 @@ namespace Bit.App.Pages
{
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
await Navigation.PopModalAsync();
#if ANDROID
if (Platform.CurrentActivity.CurrentFocus != null)
{
Platform.CurrentActivity.HideKeyboard(Platform.CurrentActivity.CurrentFocus);
}
#endif
}
private void Close_Clicked(object sender, EventArgs e)

View File

@@ -44,7 +44,7 @@ namespace Bit.App.Pages
public string WebVaultUrl { get; set; }
public string IconsUrl { get; set; }
public string NotificationsUrls { get; set; }
public Func<Task> SubmitSuccessTask { get; set; }
public Action SubmitSuccessAction { get; set; }
public Action CloseAction { get; set; }
public async Task SubmitAsync()
@@ -73,10 +73,7 @@ namespace Bit.App.Pages
IconsUrl = resUrls.Icons;
NotificationsUrls = resUrls.Notifications;
if (SubmitSuccessTask != null)
{
await SubmitSuccessTask();
}
SubmitSuccessAction?.Invoke();
}
public bool ValidateUrls()

View File

@@ -12,14 +12,12 @@ namespace Bit.App.Pages
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService;
private IConditionedAwaiterManager _conditionedAwaiterManager;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public HomePage(AppOptions appOptions = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_conditionedAwaiterManager = ServiceContainer.Resolve<IConditionedAwaiterManager>();
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
@@ -58,8 +56,6 @@ namespace Bit.App.Pages
PerformNavigationOnAccountChangedOnLoad = false;
accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
}
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.AndroidWindowCreated);
#endif
}

View File

@@ -25,6 +25,7 @@ namespace Bit.App.Pages
private bool _rememberEmail;
private string _email;
private string _selectedEnvironmentName;
private bool _displayEuEnvironment;
public HomeViewModel()
{
@@ -115,6 +116,7 @@ namespace Bit.App.Pages
{
Email = await _stateService.GetRememberedEmailAsync();
RememberEmail = !string.IsNullOrEmpty(Email);
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag, forceRefresh: true);
}
public async Task ContinueToLoginStepAsync()
@@ -156,7 +158,11 @@ namespace Bit.App.Pages
public async Task ShowEnvironmentPickerAsync()
{
var options = new string[] { BwRegion.US.Domain(), BwRegion.EU.Domain(), AppResources.SelfHosted };
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
var options = _displayEuEnvironment
? new string[] { BwRegion.US.Domain(), BwRegion.EU.Domain(), AppResources.SelfHosted }
: new string[] { BwRegion.US.Domain(), AppResources.SelfHosted };
await MainThread.InvokeOnMainThreadAsync(async () =>
{
var result = await _deviceActionService.Value.DisplayActionSheetAsync(AppResources.LoggingInOn, AppResources.Cancel, null, options);

View File

@@ -168,7 +168,7 @@ namespace Bit.App.Pages
var tasks = Task.Run(async () =>
{
await Task.Delay(50);
_vm.SubmitCommand.Execute(null);
MainThread.BeginInvokeOnMainThread(async () => await _vm.SubmitAsync());
});
}
}
@@ -232,6 +232,7 @@ namespace Bit.App.Pages
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.Core.Resources.Localization;
@@ -74,10 +73,7 @@ namespace Bit.App.Pages
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = CreateDefaultAsyncRelayCommand(
() => MainThread.InvokeOnMainThreadAsync(SubmitAsync),
onException: _logger.Exception,
allowsMultipleExecutions: false);
SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel =
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
@@ -161,7 +157,7 @@ namespace Bit.App.Pages
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ICommand SubmitCommand { get; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
@@ -237,8 +233,8 @@ namespace Bit.App.Pages
}
BiometricButtonVisible = true;
BiometricButtonText = AppResources.UseBiometricsToUnlock;
if (DeviceInfo.Platform == DevicePlatform.iOS)
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
@@ -285,8 +281,6 @@ namespace Bit.App.Pages
var failed = true;
try
{
await MainThread.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
EncString userKeyPin;
EncString oldPinProtected;
switch (_pinStatus)
@@ -335,26 +329,20 @@ namespace Bit.App.Pages
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetUserKeyAndContinueAsync(userKey, shouldHandleHideLoading: true);
await Task.Delay(150); //Workaround Delay to avoid "duplicate" execution of SubmitAsync on Android when invoked from the ReturnCommand
await SetUserKeyAndContinueAsync(userKey);
}
}
catch (LegacyUserException)
{
await MainThread.InvokeOnMainThreadAsync(_deviceActionService.HideLoadingAsync);
throw;
}
catch (Exception ex)
catch
{
_logger.Exception(ex);
failed = true;
}
if (failed)
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
await MainThread.InvokeOnMainThreadAsync(_deviceActionService.HideLoadingAsync);
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
@@ -430,7 +418,6 @@ namespace Bit.App.Pages
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetUserKeyAndContinueAsync(userKey);
await Task.Delay(150); //Workaround Delay to avoid "duplicate" execution of SubmitAsync on Android when invoked from the ReturnCommand
// Re-enable biometrics
if (BiometricEnabled & !BiometricIntegrityValid)
@@ -528,7 +515,7 @@ namespace Bit.App.Pages
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
!PinEnabled && !HasMasterPassword) ?? false;
!PinEnabled && !HasMasterPassword);
await _stateService.SetBiometricLockedAsync(!success);
if (success)
@@ -543,7 +530,7 @@ namespace Bit.App.Pages
}
}
private async Task SetUserKeyAndContinueAsync(UserKey key, bool shouldHandleHideLoading = false)
private async Task SetUserKeyAndContinueAsync(UserKey key)
{
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
@@ -551,18 +538,14 @@ namespace Bit.App.Pages
await _cryptoService.SetUserKeyAsync(key);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
await DoContinueAsync(shouldHandleHideLoading);
await DoContinueAsync();
}
private async Task DoContinueAsync(bool shouldHandleHideLoading = false)
private async Task DoContinueAsync()
{
_syncService.FullSyncAsync(false).FireAndForget();
await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
if (shouldHandleHideLoading)
{
await MainThread.InvokeOnMainThreadAsync(_deviceActionService.HideLoadingAsync);
}
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@@ -25,7 +25,6 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_vm = BindingContext as LoginPageViewModel;
_vm.Page = this;
_vm.FromIosExtension = _appOptions?.IosExtension ?? false;
_vm.StartTwoFactorAction = () => MainThread.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => MainThread.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();

View File

@@ -39,7 +39,7 @@
FontSize="Small"
FontAttributes="Bold"/>
<controls:MonoLabel
Text="{Binding LoginRequest.FingerprintPhrase}"
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
FontSize="Medium"
TextColor="{DynamicResource FingerprintPhrase}"
Margin="0,0,0,27"
@@ -85,6 +85,7 @@
<Button
Text="{u:I18n DenyLogIn}"
Command="{Binding RejectRequestCommand}"
StyleClass="btn-secundary"
AutomationId="DenyLoginButton" />
</StackLayout>

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