From 4fb811ae877a294c23aea26715ce770201d39394 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Mon, 14 Feb 2022 16:06:35 -0300 Subject: [PATCH 001/100] Build: Upload dSYMs to AppCenter (#1776) * Added dsym artifact to be uploaded alongside with the ipa o the build.yml * Added dsym artifact to be uploaded alongside with the ipa o the build.yml * Fixed build.yml dsym artifact * Fix upload dsym build.yml * Fix build.yml to check what gets exported and after this will become the adjustment for the dsym (disabling Android build for this test) * Fix build.yml to copy all dsyms and artifact them (disabling Android build for this test) * Fix build.yml to only copy all dsyms and ipa and artifact them (disabling Android build for this test) * Added Appcenterr CLI and upload missing symbols for dSYM to the build * Add secret to build workflow (#1771) * Changed build.yml upload dsym command from upload-missing-symbols to upload-symbols * Added restrictions for uploading iOS symbols to AppCenter on build.yml Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --- .github/workflows/build.yml | 53 ++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7578e3849..7d8d2475c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -366,6 +366,18 @@ jobs: - name: Checkout repo uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + - name: Login to Azure - Prod Subscription + uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403 + with: + keyvault: "bitwarden-prod-kv" + secrets: "appcenter-ios-token" + - name: Decrypt secrets env: DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }} @@ -479,13 +491,48 @@ jobs: -exportOptionsPlist $EXPORT_OPTIONS_PATH shell: bash - - name: Upload App Store .ipa artifact + - name: Copy all dSYMs files to upload + run: | + ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs" + EXPORT_PATH="./bitwarden-export" + + cp -r $ARCHIVE_DSYMS_PATH $EXPORT_PATH + shell: bash + + - name: Upload App Store .ipa & dSYMs artifacts uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4 with: - name: Bitwarden.ipa - path: ./bitwarden-export/Bitwarden.ipa + name: Bitwarden iOS + path: | + ./bitwarden-export/Bitwarden.ipa + ./bitwarden-export/dSYMs/*.* if-no-files-found: error + - name: Install AppCenter CLI + if: | + (github.ref == 'refs/heads/master' + && needs.setup.outputs.rc_branch_exists == 0 + && needs.setup.outputs.hotfix_branch_exists == 0) + || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) + || github.ref == 'refs/heads/hotfix' + uses: actions/setup-node@v2 + with: + node-version: '14' + - run: npm install -g appcenter-cli + + - name: Upload dSYMs to App Center + if: | + (github.ref == 'refs/heads/master' + && needs.setup.outputs.rc_branch_exists == 0 + && needs.setup.outputs.hotfix_branch_exists == 0) + || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) + || github.ref == 'refs/heads/hotfix' + env: + APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} + run: | + appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN + shell: bash + - name: Deploy to App Store if: | (github.ref == 'refs/heads/master' From 1af447c47f80e823b4b10389442b8e7527d4d2fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 12:37:14 -0800 Subject: [PATCH 002/100] Bumped version to 2.16.3 (#1777) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Android/Properties/AndroidManifest.xml | 2 +- src/iOS.Autofill/Info.plist | 2 +- src/iOS.Extension/Info.plist | 2 +- src/iOS.ShareExtension/Info.plist | 2 +- src/iOS/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index ac94e44cd..22bc45741 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index 580392bfe..466b423cf 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2.16.2 + 2.16.3 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index f5101b359..a3d65d8de 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2.16.2 + 2.16.3 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index b57b6f7a3..cdba626dc 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2.16.2 + 2.16.3 CFBundleVersion 1 MinimumOSVersion diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index b4d0003e7..399fda750 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2.16.2 + 2.16.3 CFBundleVersion 1 CFBundleIconName From aba34c38e93d3c57dde81bdf030f67a1f7f0502b Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Tue, 15 Feb 2022 10:46:33 -0500 Subject: [PATCH 003/100] remove erroneous autofill description (#1780) --- src/App/Resources/AppResources.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 06b132126..369fd5b70 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1150,10 +1150,10 @@ Auto-fill Accessibility Service - The Bitwarden auto-fill service uses the Android Autofill Framework to assist in filling logins, credit cards, and identity information into other apps on your device. + The Bitwarden auto-fill service uses the Android Autofill Framework to assist in filling login information into other apps on your device. - Use the Bitwarden auto-fill service to fill logins, credit cards, and identity information into other apps. + Use the Bitwarden auto-fill service to fill login information into other apps. Open Autofill Settings From 95581bd4d9dc659a6b30b52431186cec2c940724 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Tue, 15 Feb 2022 07:57:21 -0800 Subject: [PATCH 004/100] Patch/release new build artifact name (#1778) * Switching the iOS build artifact and release asset names * disabling jobs/steps to test the new release asset name * switching to download artifacts from rc * testing the upload of the 'Bitwarden iOS' directory * Build zip asset of the Bitwarden iOS asset * trying a couple of different zip paths * Final package test * Re-enabling all of the jobs after testing --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66dfb66c9..cc3e9e0e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,13 +68,16 @@ jobs: workflow_conclusion: success branch: ${{ steps.branch.outputs.branch-name }} + - name: Prep Bitwarden iOS release asset + run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS + - name: Create release uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5 with: artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, ./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk, ./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk, - ./Bitwarden.ipa/Bitwarden.ipa" + ./Bitwarden iOS.zip" commit: ${{ github.sha }} tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }} name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }} From 02562be8c74ec414fe5f9e67252fc16b3392d2e4 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Tue, 15 Feb 2022 19:10:43 -0300 Subject: [PATCH 005/100] Fix truncated bottom on Password generator when large font size is set on Android (#1782) --- src/App/Pages/Generator/GeneratorPage.xaml | 476 +++++++++++---------- src/App/Styles/Base.xaml | 2 +- 2 files changed, 241 insertions(+), 237 deletions(-) diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml index 14c32e2c4..66a04a27a 100644 --- a/src/App/Pages/Generator/GeneratorPage.xaml +++ b/src/App/Pages/Generator/GeneratorPage.xaml @@ -36,251 +36,255 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml index e8a202adf..bee9af4dc 100644 --- a/src/App/Styles/Base.xaml +++ b/src/App/Styles/Base.xaml @@ -363,7 +363,7 @@ + Value="5" /> + + + + From 4722d2f632c8910386c6cc82de0274969be26ff5 Mon Sep 17 00:00:00 2001 From: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> Date: Wed, 23 Feb 2022 08:48:07 -0600 Subject: [PATCH 017/100] Add dry run option to release workflow (#1801) * Add dry-run to release workflow. --- .github/workflows/release.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc3e9e0e7..1008015a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ on: options: - Initial Release - Redeploy + - dry-run jobs: release: @@ -21,6 +22,7 @@ jobs: branch-name: ${{ steps.branch.outputs.branch-name }} steps: - name: Branch check + if: github.event.inputs.release_type != 'dry-run' run: | if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then echo "===================================" @@ -30,12 +32,13 @@ jobs: fi - name: Checkout repo - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 - name: Retrieve Mobile release version id: retrieve-mobile-version run: | - ver=$(sed -E -n '/^ Date: Wed, 23 Feb 2022 09:34:26 -0600 Subject: [PATCH 018/100] [BEEEP] - Added workflows to ignored paths (#1802) Makes sure that edits to workflow files don't trigger a build. --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d8d2475c..34d39150e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,8 @@ on: branches-ignore: - 'l10n_master' - 'gh-pages' + paths-ignore: + - "./github/workflows/**" jobs: cloc: @@ -529,8 +531,7 @@ jobs: || github.ref == 'refs/heads/hotfix' env: APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} - run: | - appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN + run: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN shell: bash - name: Deploy to App Store From ded3f07fa6ffac4a1add422ac36b73d4c727443e Mon Sep 17 00:00:00 2001 From: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> Date: Wed, 23 Feb 2022 10:29:40 -0600 Subject: [PATCH 019/100] Fixes incorrect path in workflow (#1806) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34d39150e..e60c0884b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: - 'l10n_master' - 'gh-pages' paths-ignore: - - "./github/workflows/**" + - '.github/workflows/**' jobs: cloc: From 2e8824ce05a1b6cd6584e993114f8c956c6279ed Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:40:17 -0500 Subject: [PATCH 020/100] Account Switching (#1807) * Account Switching (#1720) * Account switching * WIP * wip * wip * updates to send test logic * fixed Send tests * fixes for theme handling on account switching and re-adding existing account * switch fixes * fixes * fixes * cleanup * vault timeout fixes * account list status enhancements * logout fixes and token handling improvements * merge latest (#1727) * remove duplicate dependency * fix for initial login token storage paradox (#1730) * Fix avatar color update toolbar item issue on iOS for account switching (#1735) * Updated account switching menu UI (#1733) * updated account switching menu UI * additional changes * add key suffix to constant * GetFirstLetters method tweaks * Fix crash on account switching when logging out when having more than user at a time (#1740) * single account migration to multi-account on app update (#1741) * Account Switching Tap to dismiss (#1743) * Added tap to dismiss on the Account switching overlay and improved a bit the code * Fix account switching overlay background transparent on the proper place * Fixed transparent background and the shadow on the account switching overlay * Fix iOS top space on Account switching list overlay after modal (#1746) * Fix top space added to Account switching list overlay after closing modal * Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well * Usability: dismiss account list on certain events (#1748) * dismiss account list on certain events * use new FireAndForget method for back button logic * Create and use Account Switching overlay control (#1753) * Added Account switching overlay control and its own ViewModel and refactored accordingly * Fix account switching Accounts list binding update * Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755) * Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756) * Usability improvements for logout on vault timeout (#1781) * accountswitching fixes (#1784) * Fix for invalid PIN lock state when switching accounts (#1792) * fix for pin lock flow * named tuple values and updated async * clear send service cache on account switch (#1796) * Global theme and account removal (#1793) * Global theme and account removal * remove redundant call to hide account list overlay * cleanup and additional tweaks * add try/catch to remove account dialog flow Co-authored-by: Federico Maccaroni --- .../Accessibility/AccessibilityService.cs | 11 +- src/Android/Android.csproj | 3 +- src/Android/Autofill/AutofillService.cs | 19 +- src/Android/Autofill/Parser.cs | 4 +- src/Android/MainActivity.cs | 8 +- src/Android/MainApplication.cs | 17 +- src/Android/Push/FirebaseMessagingService.cs | 4 +- .../Receivers/PackageReplacedReceiver.cs | 10 +- src/Android/Renderers/CustomTabbedRenderer.cs | 10 +- .../Resources/drawable/cog_environment.xml | 9 + .../drawable/{cog.xml => cog_settings.xml} | 2 +- .../AndroidPushNotificationService.cs | 13 +- src/Android/Services/ClipboardService.cs | 8 +- src/Android/Services/DeviceActionService.cs | 13 +- src/Android/Tiles/AutofillTileService.cs | 9 +- src/Android/Utilities/AppCenterHelper.cs | 8 +- src/App/App.csproj | 9 +- src/App/App.xaml.cs | 114 +- .../AccountSwitchingOverlayView.xaml | 55 + .../AccountSwitchingOverlayView.xaml.cs | 142 ++ .../AccountSwitchingOverlayViewModel.cs | 69 + .../AccountViewCell/AccountViewCell.xaml | 147 ++ .../AccountViewCell/AccountViewCell.xaml.cs | 35 + .../AccountViewCellViewModel.cs | 78 + src/App/Controls/AvatarImageSource.cs | 149 ++ src/App/Controls/ExtendedToolbarItem.cs | 29 + ...iewContentInsetAdjustmentBehaviorEffect.cs | 33 + src/App/Models/AppOptions.cs | 2 + .../Accounts/BaseChangePasswordViewModel.cs | 6 +- .../Pages/Accounts/EnvironmentPage.xaml.cs | 4 - src/App/Pages/Accounts/HomePage.xaml | 95 +- src/App/Pages/Accounts/HomePage.xaml.cs | 43 +- src/App/Pages/Accounts/HomePageViewModel.cs | 25 +- src/App/Pages/Accounts/LockPage.xaml | 249 +-- src/App/Pages/Accounts/LockPage.xaml.cs | 36 +- src/App/Pages/Accounts/LockPageViewModel.cs | 47 +- src/App/Pages/Accounts/LoginPage.xaml | 166 +- src/App/Pages/Accounts/LoginPage.xaml.cs | 87 +- src/App/Pages/Accounts/LoginPageViewModel.cs | 88 +- src/App/Pages/Accounts/LoginSsoPage.xaml.cs | 6 - .../Pages/Accounts/LoginSsoPageViewModel.cs | 22 +- src/App/Pages/Accounts/RegisterPage.xaml.cs | 8 +- .../Pages/Accounts/SetPasswordPage.xaml.cs | 8 +- .../Accounts/SetPasswordPageViewModel.cs | 15 +- src/App/Pages/Accounts/TwoFactorPage.xaml.cs | 4 - .../Pages/Accounts/TwoFactorPageViewModel.cs | 5 - .../Accounts/UpdateTempPasswordPage.xaml.cs | 3 - .../UpdateTempPasswordPageViewModel.cs | 6 +- src/App/Pages/BaseContentPage.cs | 52 +- src/App/Pages/BaseViewModel.cs | 10 +- .../Pages/Send/SendAddEditPageViewModel.cs | 8 +- .../SendGroupingsPageViewModel.cs | 10 +- .../Settings/AutofillServicesPageViewModel.cs | 9 +- .../Pages/Settings/ExtensionPageViewModel.cs | 13 +- .../Pages/Settings/OptionsPageViewModel.cs | 45 +- .../SettingsPage/SettingsPageViewModel.cs | 45 +- src/App/Pages/Settings/SyncPageViewModel.cs | 9 +- src/App/Pages/TabsPage.cs | 18 +- src/App/Pages/Vault/AddEditPage.xaml.cs | 9 +- src/App/Pages/Vault/AddEditPageViewModel.cs | 10 +- .../Pages/Vault/AttachmentsPageViewModel.cs | 12 +- .../Vault/AutofillCiphersPageViewModel.cs | 5 +- src/App/Pages/Vault/CiphersPageViewModel.cs | 3 +- .../Vault/GroupingsPage/GroupingsPage.xaml | 18 + .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 91 +- .../GroupingsPage/GroupingsPageViewModel.cs | 32 +- src/App/Pages/Vault/SharePageViewModel.cs | 8 +- src/App/Pages/Vault/ViewPageViewModel.cs | 10 +- src/App/Resources/AppResources.Designer.cs | 54 + src/App/Resources/AppResources.resx | 27 + src/App/Services/MobileStorageService.cs | 46 +- .../PushNotificationListenerService.cs | 22 +- src/App/Utilities/AppHelpers.cs | 124 +- src/App/Utilities/ThemeManager.cs | 15 +- src/Core/Abstractions/ICollectionService.cs | 2 +- src/Core/Abstractions/ICryptoService.cs | 19 +- src/Core/Abstractions/IEventService.cs | 2 +- src/Core/Abstractions/IFolderService.cs | 2 +- src/Core/Abstractions/IOrganizationService.cs | 16 + .../IPasswordGenerationService.cs | 3 +- src/Core/Abstractions/IPolicyService.cs | 10 +- src/Core/Abstractions/ISettingsService.cs | 2 +- .../Abstractions/IStateMigrationService.cs | 10 + src/Core/Abstractions/IStateService.cs | 147 +- src/Core/Abstractions/ITokenService.cs | 7 +- src/Core/Abstractions/IUserService.cs | 31 - src/Core/Abstractions/IVaultTimeoutService.cs | 23 +- src/Core/Constants.cs | 69 +- src/Core/Enums/AuthenticationStatus.cs | 9 + src/Core/Enums/StorageLocation.cs | 9 + src/Core/Enums/VaultTimeoutAction.cs | 8 + .../Models/Data}/PreviousPageInfo.cs | 2 +- src/Core/Models/Domain/Account.cs | 110 ++ src/Core/Models/Domain/State.cs | 10 + src/Core/Models/Domain/StorageOptions.cs | 13 + src/Core/Models/View/AccountView.cs | 41 + src/Core/Services/ApiService.cs | 6 +- src/Core/Services/AuthService.cs | 33 +- src/Core/Services/CipherService.cs | 90 +- src/Core/Services/CollectionService.cs | 41 +- src/Core/Services/CryptoService.cs | 119 +- src/Core/Services/EnvironmentService.cs | 14 +- src/Core/Services/EventService.cs | 26 +- src/Core/Services/FolderService.cs | 47 +- src/Core/Services/KeyConnectorService.cs | 31 +- src/Core/Services/OrganizationService.cs | 55 + .../Services/PasswordGenerationService.cs | 26 +- src/Core/Services/PolicyService.cs | 39 +- src/Core/Services/SendService.cs | 36 +- src/Core/Services/SettingsService.cs | 21 +- src/Core/Services/StateMigrationService.cs | 415 +++++ src/Core/Services/StateService.cs | 1538 ++++++++++++++++- src/Core/Services/SyncService.cs | 55 +- src/Core/Services/TokenService.cs | 116 +- src/Core/Services/TotpService.cs | 8 +- src/Core/Services/UserService.cs | 221 --- src/Core/Services/VaultTimeoutService.cs | 137 +- src/Core/Utilities/ServiceContainer.cs | 67 +- .../CredentialProviderViewController.cs | 46 +- src/iOS.Autofill/Utilities/AutofillHelpers.cs | 9 +- .../Controllers/LockPasswordViewController.cs | 54 +- ...iewContentInsetAdjustmentBehaviorEffect.cs | 37 + .../Renderers/CustomNavigationRenderer.cs | 87 + .../Renderers/CustomTabbedRenderer.cs | 24 +- src/iOS.Core/Services/ClipboardService.cs | 8 +- src/iOS.Core/Services/DeviceActionService.cs | 8 +- src/iOS.Core/Utilities/ASHelpers.cs | 12 +- src/iOS.Core/Utilities/AppCenterHelper.cs | 8 +- src/iOS.Core/Utilities/iOSCoreHelpers.cs | 19 +- src/iOS.Core/Views/ExtensionTableSource.cs | 6 +- src/iOS.Core/iOS.Core.csproj | 2 + src/iOS.Extension/LoadingViewController.cs | 25 +- src/iOS.Extension/LoginListViewController.cs | 6 +- .../LoadingViewController.cs | 26 +- src/iOS/AppDelegate.cs | 31 +- src/iOS/LaunchScreen.storyboard | 2 +- .../{cog.png => cog_environment.png} | Bin .../{cog@2x.png => cog_environment@2x.png} | Bin .../{cog@3x.png => cog_environment@3x.png} | Bin src/iOS/Resources/cog_settings.png | Bin 0 -> 486 bytes src/iOS/Resources/cog_settings@2x.png | Bin 0 -> 931 bytes src/iOS/Resources/cog_settings@3x.png | Bin 0 -> 1451 bytes src/iOS/iOS.csproj | 19 +- test/Core.Test/Services/SendServiceTests.cs | 72 +- 144 files changed, 5064 insertions(+), 1761 deletions(-) create mode 100644 src/Android/Resources/drawable/cog_environment.xml rename src/Android/Resources/drawable/{cog.xml => cog_settings.xml} (99%) create mode 100644 src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml create mode 100644 src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs create mode 100644 src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs create mode 100644 src/App/Controls/AccountViewCell/AccountViewCell.xaml create mode 100644 src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs create mode 100644 src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs create mode 100644 src/App/Controls/AvatarImageSource.cs create mode 100644 src/App/Controls/ExtendedToolbarItem.cs create mode 100644 src/App/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs create mode 100644 src/Core/Abstractions/IOrganizationService.cs create mode 100644 src/Core/Abstractions/IStateMigrationService.cs delete mode 100644 src/Core/Abstractions/IUserService.cs create mode 100644 src/Core/Enums/AuthenticationStatus.cs create mode 100644 src/Core/Enums/StorageLocation.cs create mode 100644 src/Core/Enums/VaultTimeoutAction.cs rename src/{App/Models => Core/Models/Data}/PreviousPageInfo.cs (86%) create mode 100644 src/Core/Models/Domain/Account.cs create mode 100644 src/Core/Models/Domain/State.cs create mode 100644 src/Core/Models/Domain/StorageOptions.cs create mode 100644 src/Core/Models/View/AccountView.cs create mode 100644 src/Core/Services/OrganizationService.cs create mode 100644 src/Core/Services/StateMigrationService.cs delete mode 100644 src/Core/Services/UserService.cs create mode 100644 src/iOS.Core/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs create mode 100644 src/iOS.Core/Renderers/CustomNavigationRenderer.cs rename src/iOS/Resources/{cog.png => cog_environment.png} (100%) rename src/iOS/Resources/{cog@2x.png => cog_environment@2x.png} (100%) rename src/iOS/Resources/{cog@3x.png => cog_environment@3x.png} (100%) create mode 100644 src/iOS/Resources/cog_settings.png create mode 100644 src/iOS/Resources/cog_settings@2x.png create mode 100644 src/iOS/Resources/cog_settings@3x.png diff --git a/src/Android/Accessibility/AccessibilityService.cs b/src/Android/Accessibility/AccessibilityService.cs index 7f6aad01b..1345a5cef 100644 --- a/src/Android/Accessibility/AccessibilityService.cs +++ b/src/Android/Accessibility/AccessibilityService.cs @@ -10,7 +10,6 @@ using Android.Views; using Android.Views.Accessibility; using Android.Widget; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; @@ -25,7 +24,7 @@ namespace Bit.Droid.Accessibility private const string BitwardenPackage = "com.x8bit.bitwarden"; private const string BitwardenWebsite = "vault.bitwarden.com"; - private IStorageService _storageService; + private IStateService _stateService; private IBroadcasterService _broadcasterService; private DateTime? _lastSettingsReload = null; private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1); @@ -444,9 +443,9 @@ namespace Bit.Droid.Accessibility private void LoadServices() { - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } if (_broadcasterService == null) { @@ -460,12 +459,12 @@ namespace Bit.Droid.Accessibility if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan) { _lastSettingsReload = now; - var uris = await _storageService.GetAsync>(Constants.AutofillBlacklistedUrisKey); + var uris = await _stateService.GetAutofillBlacklistedUrisAsync(); if (uris != null) { _blacklistedUris = new HashSet(uris); } - var isAutoFillTileAdded = await _storageService.GetAsync(Constants.AutofillTileAdded); + var isAutoFillTileAdded = await _stateService.GetAutofillTileAddedAsync(); AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault(); } } diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 462afcaf9..52e4f9646 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -171,7 +171,8 @@ - + + diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs index 0d0b1cc07..c339794bc 100644 --- a/src/Android/Autofill/AutofillService.cs +++ b/src/Android/Autofill/AutofillService.cs @@ -26,9 +26,8 @@ namespace Bit.Droid.Autofill { private ICipherService _cipherService; private IVaultTimeoutService _vaultTimeoutService; - private IStorageService _storageService; private IPolicyService _policyService; - private IUserService _userService; + private IStateService _stateService; public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) @@ -44,18 +43,18 @@ namespace Bit.Droid.Autofill var parser = new Parser(structure, ApplicationContext); parser.Parse(); - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } - var shouldAutofill = await parser.ShouldAutofillAsync(_storageService); + var shouldAutofill = await parser.ShouldAutofillAsync(_stateService); if (!shouldAutofill) { return; } - var inlineAutofillEnabled = await _storageService.GetAsync(Constants.InlineAutofillEnabledKey) ?? true; + var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true; if (_vaultTimeoutService == null) { @@ -76,7 +75,7 @@ namespace Bit.Droid.Autofill // build response var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request); - var disableSavePrompt = await _storageService.GetAsync(Constants.AutofillDisableSavePromptKey); + var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync(); if (!disableSavePrompt.GetValueOrDefault()) { AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection); @@ -101,12 +100,12 @@ namespace Bit.Droid.Autofill return; } - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } - var disableSavePrompt = await _storageService.GetAsync(Constants.AutofillDisableSavePromptKey); + var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync(); if (disableSavePrompt.GetValueOrDefault()) { return; diff --git a/src/Android/Autofill/Parser.cs b/src/Android/Autofill/Parser.cs index d097d4d1f..45a2fdb0b 100644 --- a/src/Android/Autofill/Parser.cs +++ b/src/Android/Autofill/Parser.cs @@ -80,13 +80,13 @@ namespace Bit.Droid.Autofill } } - public async Task ShouldAutofillAsync(IStorageService storageService) + public async Task ShouldAutofillAsync(IStateService stateService) { var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) && FieldCollection != null && FieldCollection.Fillable; if (fillable) { - var blacklistedUris = await storageService.GetAsync>(Constants.AutofillBlacklistedUrisKey); + var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync(); if (blacklistedUris != null && blacklistedUris.Count > 0) { fillable = !new HashSet(blacklistedUris).Contains(Uri); diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index c44c83a3a..c11f8b9f7 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -32,7 +32,7 @@ namespace Bit.Droid private IDeviceActionService _deviceActionService; private IMessagingService _messagingService; private IBroadcasterService _broadcasterService; - private IUserService _userService; + private IStateService _stateService; private IAppIdService _appIdService; private IEventService _eventService; private PendingIntent _eventUploadPendingIntent; @@ -53,7 +53,7 @@ namespace Bit.Droid _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _messagingService = ServiceContainer.Resolve("messagingService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _appIdService = ServiceContainer.Resolve("appIdService"); _eventService = ServiceContainer.Resolve("eventService"); @@ -70,7 +70,7 @@ namespace Bit.Droid } #if !FDROID - var appCenterHelper = new AppCenterHelper(_appIdService, _userService); + var appCenterHelper = new AppCenterHelper(_appIdService, _stateService); var appCenterTask = appCenterHelper.InitAsync(); #endif @@ -375,7 +375,7 @@ namespace Bit.Droid { Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor); Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor); - ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled()); + ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled()); } private void ExitApp() diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index 1957f70a2..ff2fd307a 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -113,13 +113,16 @@ namespace Bit.Droid var secureStorageService = new SecureStorageService(); var cryptoPrimitiveService = new CryptoPrimitiveService(); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); - var deviceActionService = new DeviceActionService(mobileStorageService, messagingService, + var stateService = new StateService(mobileStorageService, secureStorageService); + var stateMigrationService = + new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService); + var deviceActionService = new DeviceActionService(stateService, messagingService, broadcasterService, () => ServiceContainer.Resolve("eventService")); var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, broadcasterService); var biometricService = new BiometricService(); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); - var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService); + var cryptoService = new CryptoService(stateService, cryptoFunctionService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); ServiceContainer.Register("broadcasterService", broadcasterService); @@ -129,7 +132,9 @@ namespace Bit.Droid ServiceContainer.Register("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register("storageService", mobileStorageService); ServiceContainer.Register("secureStorageService", secureStorageService); - ServiceContainer.Register("clipboardService", new ClipboardService(mobileStorageService)); + ServiceContainer.Register("stateService", stateService); + ServiceContainer.Register("stateMigrationService", stateMigrationService); + ServiceContainer.Register("clipboardService", new ClipboardService(stateService)); ServiceContainer.Register("deviceActionService", deviceActionService); ServiceContainer.Register("platformUtilsService", platformUtilsService); ServiceContainer.Register("biometricService", biometricService); @@ -148,7 +153,7 @@ namespace Bit.Droid ServiceContainer.Register( "pushNotificationListenerService", notificationListenerService); var androidPushNotificationService = new AndroidPushNotificationService( - mobileStorageService, notificationListenerService); + stateService, notificationListenerService); ServiceContainer.Register( "pushNotificationService", androidPushNotificationService); #endif @@ -164,10 +169,6 @@ namespace Bit.Droid private async Task BootstrapAsync() { - var disableFavicon = await ServiceContainer.Resolve("storageService") - .GetAsync(Constants.DisableFaviconKey); - await ServiceContainer.Resolve("stateService").SaveAsync( - Constants.DisableFaviconKey, disableFavicon); await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync(); } } diff --git a/src/Android/Push/FirebaseMessagingService.cs b/src/Android/Push/FirebaseMessagingService.cs index 5351436c2..676390aef 100644 --- a/src/Android/Push/FirebaseMessagingService.cs +++ b/src/Android/Push/FirebaseMessagingService.cs @@ -16,10 +16,10 @@ namespace Bit.Droid.Push { public async override void OnNewToken(string token) { - var storageService = ServiceContainer.Resolve("storageService"); + var stateService = ServiceContainer.Resolve("stateService"); var pushNotificationService = ServiceContainer.Resolve("pushNotificationService"); - await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token); + await stateService.SetPushRegisteredTokenAsync(token); await pushNotificationService.RegisterAsync(); } diff --git a/src/Android/Receivers/PackageReplacedReceiver.cs b/src/Android/Receivers/PackageReplacedReceiver.cs index 2af350320..a4a03f9c1 100644 --- a/src/Android/Receivers/PackageReplacedReceiver.cs +++ b/src/Android/Receivers/PackageReplacedReceiver.cs @@ -1,5 +1,4 @@ -using System; -using Android.App; +using Android.App; using Android.Content; using Bit.App.Abstractions; using Bit.App.Utilities; @@ -14,9 +13,10 @@ namespace Bit.Droid.Receivers { public override async void OnReceive(Context context, Intent intent) { - var storageService = ServiceContainer.Resolve("storageService"); - await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve("syncService"), - ServiceContainer.Resolve("deviceActionService"), storageService); + await AppHelpers.PerformUpdateTasksAsync( + ServiceContainer.Resolve("syncService"), + ServiceContainer.Resolve("deviceActionService"), + ServiceContainer.Resolve("stateService")); } } } diff --git a/src/Android/Renderers/CustomTabbedRenderer.cs b/src/Android/Renderers/CustomTabbedRenderer.cs index ee11f56a2..dea31033a 100644 --- a/src/Android/Renderers/CustomTabbedRenderer.cs +++ b/src/Android/Renderers/CustomTabbedRenderer.cs @@ -1,7 +1,9 @@ using Android.Content; using Android.Views; +using Bit.App.Pages; using Bit.Droid.Renderers; using Google.Android.Material.BottomNavigation; +using Google.Android.Material.Navigation; using Xamarin.Forms; using Xamarin.Forms.Platform.Android; using Xamarin.Forms.Platform.Android.AppCompat; @@ -9,7 +11,7 @@ using Xamarin.Forms.Platform.Android.AppCompat; [assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))] namespace Bit.Droid.Renderers { - public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener + public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener { private TabbedPage _page; @@ -21,7 +23,7 @@ namespace Bit.Droid.Renderers if (e.NewElement != null) { _page = e.NewElement; - GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this); + GetBottomNavigationView()?.SetOnItemReselectedListener(this); } else { @@ -53,6 +55,10 @@ namespace Bit.Droid.Renderers { if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0) { + if (_page is TabsPage tabsPage) + { + tabsPage.OnPageReselected(); + } Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync()); } } diff --git a/src/Android/Resources/drawable/cog_environment.xml b/src/Android/Resources/drawable/cog_environment.xml new file mode 100644 index 000000000..6e5b4ff76 --- /dev/null +++ b/src/Android/Resources/drawable/cog_environment.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/src/Android/Resources/drawable/cog.xml b/src/Android/Resources/drawable/cog_settings.xml similarity index 99% rename from src/Android/Resources/drawable/cog.xml rename to src/Android/Resources/drawable/cog_settings.xml index 311d0ad0c..b8c3ba603 100644 --- a/src/Android/Resources/drawable/cog.xml +++ b/src/Android/Resources/drawable/cog_settings.xml @@ -6,4 +6,4 @@ - + \ No newline at end of file diff --git a/src/Android/Services/AndroidPushNotificationService.cs b/src/Android/Services/AndroidPushNotificationService.cs index 097c5f708..e871393f6 100644 --- a/src/Android/Services/AndroidPushNotificationService.cs +++ b/src/Android/Services/AndroidPushNotificationService.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; using AndroidX.Core.App; using Bit.App.Abstractions; -using Bit.Core; using Bit.Core.Abstractions; using Xamarin.Forms; @@ -11,14 +10,14 @@ namespace Bit.Droid.Services { public class AndroidPushNotificationService : IPushNotificationService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IPushNotificationListenerService _pushNotificationListenerService; public AndroidPushNotificationService( - IStorageService storageService, + IStateService stateService, IPushNotificationListenerService pushNotificationListenerService) { - _storageService = storageService; + _stateService = stateService; _pushNotificationListenerService = pushNotificationListenerService; } @@ -26,12 +25,12 @@ namespace Bit.Droid.Services public async Task GetTokenAsync() { - return await _storageService.GetAsync(Constants.PushCurrentTokenKey); + return await _stateService.GetPushCurrentTokenAsync(); } public async Task RegisterAsync() { - var registeredToken = await _storageService.GetAsync(Constants.PushRegisteredTokenKey); + var registeredToken = await _stateService.GetPushRegisteredTokenAsync(); var currentToken = await GetTokenAsync(); if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken) { @@ -39,7 +38,7 @@ namespace Bit.Droid.Services } else { - await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow); + await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow); } } diff --git a/src/Android/Services/ClipboardService.cs b/src/Android/Services/ClipboardService.cs index 82f1c21db..07e720c57 100644 --- a/src/Android/Services/ClipboardService.cs +++ b/src/Android/Services/ClipboardService.cs @@ -12,12 +12,12 @@ namespace Bit.Droid.Services { public class ClipboardService : IClipboardService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly Lazy _clearClipboardPendingIntent; - public ClipboardService(IStorageService storageService) + public ClipboardService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; _clearClipboardPendingIntent = new Lazy(() => PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity, @@ -39,7 +39,7 @@ namespace Bit.Droid.Services if (clearMs < 0) { // if not set then we need to check if the user set this config - var clearSeconds = await _storageService.GetAsync(Constants.ClearClipboardKey); + var clearSeconds = await _stateService.GetClearClipboardAsync(); if (clearSeconds != null) { clearMs = clearSeconds.Value * 1000; diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index c0e5a79e1..4a3e52d22 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -35,7 +35,7 @@ namespace Bit.Droid.Services { public class DeviceActionService : IDeviceActionService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IMessagingService _messagingService; private readonly IBroadcasterService _broadcasterService; private readonly Func _eventServiceFunc; @@ -47,12 +47,12 @@ namespace Bit.Droid.Services private string _userAgent; public DeviceActionService( - IStorageService storageService, + IStateService stateService, IMessagingService messagingService, IBroadcasterService broadcasterService, Func eventServiceFunc) { - _storageService = storageService; + _stateService = stateService; _messagingService = messagingService; _broadcasterService = broadcasterService; _eventServiceFunc = eventServiceFunc; @@ -333,7 +333,7 @@ namespace Bit.Droid.Services try { DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir); - await _storageService.SaveAsync(Constants.LastFileCacheClearKey, DateTime.UtcNow); + await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow); } catch (Exception) { } } @@ -916,9 +916,8 @@ namespace Bit.Droid.Services { if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp)) { - var userService = ServiceContainer.Resolve("userService"); - var autoCopyDisabled = await _storageService.GetAsync(Constants.DisableAutoTotpCopyKey); - var canAccessPremium = await userService.CanAccessPremiumAsync(); + var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync(); + var canAccessPremium = await _stateService.CanAccessPremiumAsync(); if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault()) { var totpService = ServiceContainer.Resolve("totpService"); diff --git a/src/Android/Tiles/AutofillTileService.cs b/src/Android/Tiles/AutofillTileService.cs index 305afce37..7f9af7d8b 100644 --- a/src/Android/Tiles/AutofillTileService.cs +++ b/src/Android/Tiles/AutofillTileService.cs @@ -4,7 +4,6 @@ using Android.Content; using Android.Runtime; using Android.Service.QuickSettings; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; using Bit.Droid.Accessibility; @@ -18,7 +17,7 @@ namespace Bit.Droid.Tile [Register("com.x8bit.bitwarden.AutofillTileService")] public class AutofillTileService : TileService { - private IStorageService _storageService; + private IStateService _stateService; public override void OnTileAdded() { @@ -59,11 +58,11 @@ namespace Bit.Droid.Tile private void SetTileAdded(bool isAdded) { AccessibilityHelpers.IsAutofillTileAdded = isAdded; - if (_storageService == null) + if (_stateService == null) { - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); } - _storageService.SaveAsync(Constants.AutofillTileAdded, isAdded); + _stateService.SetAutofillTileAddedAsync(isAdded); } private void ScanAndFill() diff --git a/src/Android/Utilities/AppCenterHelper.cs b/src/Android/Utilities/AppCenterHelper.cs index d580fbbc6..aa3313a3e 100644 --- a/src/Android/Utilities/AppCenterHelper.cs +++ b/src/Android/Utilities/AppCenterHelper.cs @@ -12,22 +12,22 @@ namespace Bit.Droid.Utilities private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42"; private readonly IAppIdService _appIdService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private string _userId; private string _appId; public AppCenterHelper( IAppIdService appIdService, - IUserService userService) + IStateService stateService) { _appIdService = appIdService; - _userService = userService; + _stateService = stateService; } public async Task InitAsync() { - _userId = await _userService.GetUserIdAsync(); + _userId = await _stateService.GetActiveUserIdAsync(); _appId = await _appIdService.GetAppIdAsync(); AppCenter.Start(AppSecret, typeof(Crashes)); diff --git a/src/App/App.csproj b/src/App/App.csproj index b0916f2ed..b335c3a11 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -15,12 +15,13 @@ + + - + - @@ -121,17 +122,20 @@ SendGroupingsPage.xaml Code + + MSBuild:UpdateDesignTimeXaml + @@ -416,5 +420,6 @@ + diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 44c6531f8..0dada2232 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -4,12 +4,12 @@ using Bit.App.Pages; using Bit.App.Resources; using Bit.App.Services; using Bit.App.Utilities; -using Bit.Core; using Bit.Core.Abstractions; +using Bit.Core.Models.Data; using Bit.Core.Utilities; using System; -using System.Threading; using System.Threading.Tasks; +using Bit.Core.Enums; using Xamarin.Forms; using Xamarin.Forms.Xaml; @@ -18,7 +18,6 @@ namespace Bit.App { public partial class App : Application { - private readonly IUserService _userService; private readonly IBroadcasterService _broadcasterService; private readonly IMessagingService _messagingService; private readonly IStateService _stateService; @@ -26,7 +25,6 @@ namespace Bit.App private readonly ISyncService _syncService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuthService _authService; - private readonly IStorageService _storageService; private readonly IStorageService _secureStorageService; private readonly IDeviceActionService _deviceActionService; @@ -40,7 +38,6 @@ namespace Bit.App Current = this; return; } - _userService = ServiceContainer.Resolve("userService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _messagingService = ServiceContainer.Resolve("messagingService"); _stateService = ServiceContainer.Resolve("stateService"); @@ -48,7 +45,6 @@ namespace Bit.App _syncService = ServiceContainer.Resolve("syncService"); _authService = ServiceContainer.Resolve("authService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _storageService = ServiceContainer.Resolve("storageService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -85,8 +81,12 @@ namespace Bit.App } else if (message.Command == "logout") { + var extras = message.Data as Tuple; + var userId = extras?.Item1; + var userInitiated = extras?.Item2; + var expired = extras?.Item3; Device.BeginInvokeOnMainThread(async () => - await LogOutAsync((message.Data as bool?).GetValueOrDefault())); + await LogOutAsync(userId, userInitiated, expired)); } else if (message.Command == "loggedOut") { @@ -107,6 +107,18 @@ namespace Bit.App await SleptAsync(); } } + else if (message.Command == "addAccount") + { + await AddAccount(); + } + else if (message.Command == "accountAdded") + { + await UpdateThemeAsync(); + } + else if (message.Command == "switchedAccount") + { + await SwitchedAccountAsync(); + } else if (message.Command == "migrated") { await Task.Delay(1000); @@ -168,7 +180,7 @@ namespace Bit.App if (string.IsNullOrWhiteSpace(Options.Uri)) { var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, - _storageService); + _stateService); if (!updated) { SyncIfNeeded(); @@ -192,7 +204,7 @@ namespace Bit.App var isLocked = await _vaultTimeoutService.IsLockedAsync(); if (!isLocked) { - await _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime()); + await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); } SetTabsPageFromAutofill(isLocked); await SleptAsync(); @@ -233,7 +245,7 @@ namespace Bit.App { await Device.InvokeOnMainThreadAsync(() => { - ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); + ThemeManager.SetTheme(Current.Resources); _messagingService.Send("updatedTheme"); }); } @@ -246,27 +258,70 @@ namespace Bit.App new System.Globalization.UmAlQuraCalendar(); } - private async Task LogOutAsync(bool expired) + private async Task LogOutAsync(string userId, bool? userInitiated, bool? expired) { - await AppHelpers.LogOutAsync(); + await AppHelpers.LogOutAsync(userId, userInitiated.GetValueOrDefault(true)); + await SetMainPageAsync(); _authService.LogOut(() => { - Current.MainPage = new HomePage(); - if (expired) + if (expired.GetValueOrDefault()) { _platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired); } }); } + private async Task AddAccount() + { + Device.BeginInvokeOnMainThread(async () => + { + Options.HideAccountSwitcher = false; + Current.MainPage = new NavigationPage(new HomePage(Options)); + }); + } + + private async Task SwitchedAccountAsync() + { + await AppHelpers.OnAccountSwitchAsync(); + var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync(); + Device.BeginInvokeOnMainThread(async () => + { + if (shouldTimeout) + { + await _vaultTimeoutService.ExecuteTimeoutActionAsync(); + } + else + { + await SetMainPageAsync(); + } + await Task.Delay(50); + await UpdateThemeAsync(); + }); + } + private async Task SetMainPageAsync() { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (authed) { - if (await _vaultTimeoutService.IsLockedAsync()) + var isLocked = await _vaultTimeoutService.IsLockedAsync(); + var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync(); + if (isLocked || shouldTimeout) { - Current.MainPage = new NavigationPage(new LockPage(Options)); + var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync(); + if (vaultTimeoutAction == VaultTimeoutAction.Logout) + { + // TODO implement orgIdentifier flow to SSO Login page, same as email flow below + // var orgIdentifier = await _stateService.GetOrgIdentifierAsync(); + + var email = await _stateService.GetEmailAsync(); + Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null; + Current.MainPage = new NavigationPage(new LoginPage(email, Options)); + } + else + { + Current.MainPage = new NavigationPage(new LockPage(Options)); + } } else if (Options.FromAutofillFramework && Options.SaveType.HasValue) { @@ -287,13 +342,25 @@ namespace Bit.App } else { - Current.MainPage = new HomePage(Options); + Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null; + if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync()) + { + // TODO implement orgIdentifier flow to SSO Login page, same as email flow below + // var orgIdentifier = await _stateService.GetOrgIdentifierAsync(); + + var email = await _stateService.GetEmailAsync(); + Current.MainPage = new NavigationPage(new LoginPage(email, Options)); + } + else + { + Current.MainPage = new NavigationPage(new HomePage(Options)); + } } } private async Task ClearCacheIfNeededAsync() { - var lastClear = await _storageService.GetAsync(Constants.LastFileCacheClearKey); + var lastClear = await _stateService.GetLastFileCacheClearAsync(); if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1) { var task = Task.Run(() => _deviceActionService.ClearCacheAsync()); @@ -336,12 +403,12 @@ namespace Bit.App { InitializeComponent(); SetCulture(); - ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); + ThemeManager.SetTheme(Current.Resources); Current.RequestedThemeChanged += (s, a) => { UpdateThemeAsync(); }; - Current.MainPage = new HomePage(); + Current.MainPage = new NavigationPage(new HomePage(Options)); var mainPageTask = SetMainPageAsync(); ServiceContainer.Resolve("platformUtilsService").Init(); } @@ -365,10 +432,9 @@ namespace Bit.App private async Task LockedAsync(bool autoPromptBiometric) { - await _stateService.PurgeAsync(); if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS) { - var vaultTimeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); + var vaultTimeout = await _stateService.GetVaultTimeoutAsync(); if (vaultTimeout == 0) { autoPromptBiometric = false; @@ -398,7 +464,7 @@ namespace Bit.App } } } - await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock); + await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock); var lockPage = new LockPage(Options, autoPromptBiometric); Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage)); } diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml new file mode 100644 index 000000000..0edbf4503 --- /dev/null +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs new file mode 100644 index 000000000..8f213dcba --- /dev/null +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs @@ -0,0 +1,142 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; +#if !FDROID +using Microsoft.AppCenter.Crashes; +#endif +using Xamarin.CommunityToolkit.ObjectModel; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public partial class AccountSwitchingOverlayView : ContentView + { + public static readonly BindableProperty MainFabProperty = BindableProperty.Create( + nameof(MainFab), + typeof(View), + typeof(AccountSwitchingOverlayView), + defaultBindingMode: BindingMode.OneWay); + + public View MainFab + { + get => (View)GetValue(MainFabProperty); + set => SetValue(MainFabProperty, value); + } + + public AccountSwitchingOverlayView() + { + InitializeComponent(); + + ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync, +#if !FDROID + onException: ex => Crashes.TrackError(ex), +#endif + allowsMultipleExecutions: false); + } + + public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel; + + public ICommand ToggleVisibililtyCommand { get; } + + public async Task ToggleVisibilityAsync() + { + if (IsVisible) + { + await HideAsync(); + } + else + { + await ShowAsync(); + } + } + + public async Task ShowAsync() + { + await ViewModel?.RefreshAccountViewsAsync(); + + await Device.InvokeOnMainThreadAsync(async () => + { + // start listView in default (off-screen) position + await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0); + + // set overlay opacity to zero before making visible and start fade-in + Opacity = 0; + IsVisible = true; + this.FadeTo(1, 100); + + if (Device.RuntimePlatform == Device.Android && MainFab != null) + { + // start fab fade-out + MainFab.FadeTo(0, 200); + } + + // slide account list into view + await _accountListContainer.TranslateTo(0, 0, 200, Easing.SinOut); + }); + } + + public async Task HideAsync() + { + if (!IsVisible) + { + // already hidden, don't animate again + return; + } + // Not all animations are awaited. This is intentional to allow multiple simultaneous animations. + await Device.InvokeOnMainThreadAsync(async () => + { + // start overlay fade-out + this.FadeTo(0, 200); + + if (Device.RuntimePlatform == Device.Android && MainFab != null) + { + // start fab fade-in + MainFab.FadeTo(1, 200); + } + + // slide account list out of view + await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 200, Easing.SinIn); + + // remove overlay + IsVisible = false; + }); + } + + private async void FreeSpaceOverlay_Tapped(object sender, EventArgs e) + { + try + { + await HideAsync(); + } + catch (Exception ex) + { +#if !FDROID + Crashes.TrackError(ex); +#endif + } + } + + async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e) + { + try + { + if (!(e.SelectedItem is AccountViewCellViewModel item)) + { + return; + } + + ((ListView)sender).SelectedItem = null; + await Task.Delay(100); + await HideAsync(); + + ViewModel?.SelectAccountCommand?.Execute(item); + } + catch (Exception ex) + { +#if !FDROID + Crashes.TrackError(ex); +#endif + } + } + } +} diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs new file mode 100644 index 000000000..bc15fe96a --- /dev/null +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows.Input; +using Bit.Core.Abstractions; +using Bit.Core.Models.View; +using Bit.Core.Utilities; +using Microsoft.AppCenter.Crashes; +using Xamarin.CommunityToolkit.ObjectModel; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class AccountSwitchingOverlayViewModel : ExtendedViewModel + { + private readonly IStateService _stateService; + private readonly IMessagingService _messagingService; + + public AccountSwitchingOverlayViewModel(IStateService stateService, + IMessagingService messagingService) + { + _stateService = stateService; + _messagingService = messagingService; + + SelectAccountCommand = new AsyncCommand(SelectAccountAsync, +#if !FDROID + onException: ex => Crashes.TrackError(ex), +#endif + allowsMultipleExecutions: false); + } + + // this needs to be a new list every time for the binding to get updated, + // XF doesn't currentlyl provide a direct way to update on same instance + // https://github.com/xamarin/Xamarin.Forms/issues/1950 + public List AccountViews => _stateService?.AccountViews is null ? null : new List(_stateService.AccountViews); + + public bool AllowActiveAccountSelection { get; set; } + + public bool AllowAddAccountRow { get; set; } + + public ICommand SelectAccountCommand { get; } + + private async Task SelectAccountAsync(AccountViewCellViewModel item) + { + if (item.AccountView.IsAccount) + { + if (!item.AccountView.IsActive) + { + await _stateService.SetActiveUserAsync(item.AccountView.UserId); + _messagingService.Send("switchedAccount"); + } + else if (AllowActiveAccountSelection) + { + _messagingService.Send("switchedAccount"); + } + } + else + { + _messagingService.Send("addAccount"); + } + } + + public async Task RefreshAccountViewsAsync() + { + await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow); + + Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(AccountViews))); + } + } +} diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml b/src/App/Controls/AccountViewCell/AccountViewCell.xaml new file mode 100644 index 000000000..77773919a --- /dev/null +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs b/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs new file mode 100644 index 000000000..8195b429b --- /dev/null +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs @@ -0,0 +1,35 @@ +using Bit.Core.Models.View; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public partial class AccountViewCell : ViewCell + { + public static readonly BindableProperty AccountProperty = BindableProperty.Create( + nameof(Account), typeof(AccountView), typeof(AccountViewCell), default(AccountView), BindingMode.OneWay); + + public AccountViewCell() + { + InitializeComponent(); + } + + public AccountView Account + { + get => GetValue(AccountProperty) as AccountView; + set => SetValue(AccountProperty, value); + } + + protected override void OnPropertyChanged(string propertyName = null) + { + base.OnPropertyChanged(propertyName); + if (propertyName == AccountProperty.PropertyName) + { + if (Account == null) + { + return; + } + BindingContext = new AccountViewCellViewModel(Account); + } + } + } +} diff --git a/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs new file mode 100644 index 000000000..3c7d96f13 --- /dev/null +++ b/src/App/Controls/AccountViewCell/AccountViewCellViewModel.cs @@ -0,0 +1,78 @@ +using Bit.Core; +using Bit.Core.Enums; +using Bit.Core.Models.View; +using Bit.Core.Utilities; + +namespace Bit.App.Controls +{ + public class AccountViewCellViewModel : ExtendedViewModel + { + private AccountView _accountView; + private AvatarImageSource _avatar; + + public AccountViewCellViewModel(AccountView accountView) + { + AccountView = accountView; + AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email); + } + + public AccountView AccountView + { + get => _accountView; + set => SetProperty(ref _accountView, value); + } + + public AvatarImageSource AvatarImageSource + { + get => _avatar; + set => SetProperty(ref _avatar, value); + } + + public bool IsAccount + { + get => AccountView.IsAccount; + } + + public bool ShowHostname + { + get => !string.IsNullOrWhiteSpace(AccountView.Hostname) && AccountView.Hostname != "vault.bitwarden.com"; + } + + public bool IsActive + { + get => AccountView.IsActive; + } + + public bool IsUnlocked + { + get => AccountView.AuthStatus == AuthenticationStatus.Unlocked; + } + + public bool IsLocked + { + get => AccountView.AuthStatus == AuthenticationStatus.Locked; + } + + public bool IsLoggedOut + { + get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut; + } + + public string AuthStatusIconActive + { + get => BitwardenIcons.CheckCircle; + } + + public string AuthStatusIconNotActive + { + get + { + if (IsUnlocked) + { + return BitwardenIcons.Unlock; + } + return BitwardenIcons.Lock; + } + } + } +} diff --git a/src/App/Controls/AvatarImageSource.cs b/src/App/Controls/AvatarImageSource.cs new file mode 100644 index 000000000..1ebe3509a --- /dev/null +++ b/src/App/Controls/AvatarImageSource.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SkiaSharp; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class AvatarImageSource : StreamImageSource + { + private string _data; + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (obj is AvatarImageSource avatar) + { + return avatar._data == _data; + } + + return base.Equals(obj); + } + + public override int GetHashCode() => _data?.GetHashCode() ?? -1; + + public AvatarImageSource(string name = null, string email = null) + { + _data = name; + if (string.IsNullOrWhiteSpace(_data)) + { + _data = email; + } + } + + public override Func> Stream => GetStreamAsync; + + private Task GetStreamAsync(CancellationToken userToken = new CancellationToken()) + { + OnLoadingStarted(); + userToken.Register(CancellationTokenSource.Cancel); + var result = Draw(); + OnLoadingCompleted(CancellationTokenSource.IsCancellationRequested); + return Task.FromResult(result); + } + + private Stream Draw() + { + string chars = null; + string upperData = null; + + if (string.IsNullOrEmpty(_data)) + { + chars = ".."; + } + else if (_data?.Length > 1) + { + upperData = _data.ToUpper(); + chars = GetFirstLetters(upperData, 2); + } + + var bgColor = StringToColor(upperData); + var textColor = Color.White; + var size = 50; + + var bitmap = new SKBitmap( + size * 2, + size * 2, + SKImageInfo.PlatformColorType, + SKAlphaType.Premul); + var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Transparent); + + var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2; + var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2; + var radius = midX - midX / 5; + + var circlePaint = new SKPaint + { + IsAntialias = true, + Style = SKPaintStyle.Fill, + StrokeJoin = SKStrokeJoin.Miter, + Color = SKColor.Parse(bgColor.ToHex()) + }; + canvas.DrawCircle(midX, midY, radius, circlePaint); + + var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal); + var textSize = midX / 1.3f; + var textPaint = new SKPaint + { + IsAntialias = true, + Style = SKPaintStyle.Fill, + Color = SKColor.Parse(textColor.ToHex()), + TextSize = textSize, + TextAlign = SKTextAlign.Center, + Typeface = typeface + }; + var rect = new SKRect(); + textPaint.MeasureText(chars, ref rect); + canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint); + + return SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100).AsStream(); + } + + private string GetFirstLetters(string data, int charCount) + { + var parts = data.Split(); + if (parts.Length > 1 && charCount <= 2) + { + var text = ""; + for (int i = 0; i < charCount; i++) + { + text += parts[i].Substring(0,1); + } + return text; + } + if (data.Length > 2) + { + return data.Substring(0, 2); + } + return null; + } + + private Color StringToColor(string str) + { + if (str == null) + { + return Color.FromHex("#33ffffff"); + } + var hash = 0; + for (var i = 0; i < str.Length; i++) + { + hash = str[i] + ((hash << 5) - hash); + } + var color = "#FF"; + for (var i = 0; i < 3; i++) + { + var value = (hash >> (i * 8)) & 0xff; + var base16 = "00" + Convert.ToString(value, 16); + color += base16.Substring(base16.Length - 2); + } + return Color.FromHex(color); + } + } +} diff --git a/src/App/Controls/ExtendedToolbarItem.cs b/src/App/Controls/ExtendedToolbarItem.cs new file mode 100644 index 000000000..9c6efe59a --- /dev/null +++ b/src/App/Controls/ExtendedToolbarItem.cs @@ -0,0 +1,29 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class ExtendedToolbarItem : ToolbarItem + { + public bool UseOriginalImage { get; set; } + + // HACK: For the issue of correctly updating the avatar toolbar item color on iOS + // we need to subscribe to the PropertyChanged event of the ToolbarItem on the CustomNavigationRenderer + // The problem is that there are a lot of private places where the navigation renderer disposes objects + // that we don't have access to, and that we should in order to properly prevent memory leaks + // So as a hack solution we have this OnAppearing/OnDisappearing actions and methods to be called on page lifecycle + // to subscribe/unsubscribe indirectly on the CustomNavigationRenderer + public Action OnAppearingAction { get; set; } + public Action OnDisappearingAction { get; set; } + + public void OnAppearing() + { + OnAppearingAction?.Invoke(); + } + + public void OnDisappearing() + { + OnDisappearingAction?.Invoke(); + } + } +} diff --git a/src/App/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs b/src/App/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs new file mode 100644 index 000000000..c80199c84 --- /dev/null +++ b/src/App/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs @@ -0,0 +1,33 @@ +using Xamarin.Forms; + +namespace Bit.App.Effects +{ + public enum ScrollContentInsetAdjustmentBehavior + { + Automatic, + ScrollableAxes, + Never, + Always + } + + public class ScrollViewContentInsetAdjustmentBehaviorEffect : RoutingEffect + { + public static readonly BindableProperty ContentInsetAdjustmentBehaviorProperty = + BindableProperty.CreateAttached("ContentInsetAdjustmentBehavior", typeof(ScrollContentInsetAdjustmentBehavior), typeof(ScrollViewContentInsetAdjustmentBehaviorEffect), ScrollContentInsetAdjustmentBehavior.Automatic); + + public static ScrollContentInsetAdjustmentBehavior GetContentInsetAdjustmentBehavior(BindableObject view) + { + return (ScrollContentInsetAdjustmentBehavior)view.GetValue(ContentInsetAdjustmentBehaviorProperty); + } + + public static void SetContentInsetAdjustmentBehavior(BindableObject view, ScrollContentInsetAdjustmentBehavior value) + { + view.SetValue(ContentInsetAdjustmentBehaviorProperty, value); + } + + public ScrollViewContentInsetAdjustmentBehaviorEffect() + : base($"Bitwarden.{nameof(ScrollViewContentInsetAdjustmentBehaviorEffect)}") + { + } + } +} diff --git a/src/App/Models/AppOptions.cs b/src/App/Models/AppOptions.cs index 7d7e2b22c..0a251d7cd 100644 --- a/src/App/Models/AppOptions.cs +++ b/src/App/Models/AppOptions.cs @@ -22,6 +22,7 @@ namespace Bit.App.Models public bool IosExtension { get; set; } public Tuple CreateSend { get; set; } public bool CopyInsteadOfShareAfterSaving { get; set; } + public bool HideAccountSwitcher { get; set; } public void SetAllFrom(AppOptions o) { @@ -46,6 +47,7 @@ namespace Bit.App.Models IosExtension = o.IosExtension; CreateSend = o.CreateSend; CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving; + HideAccountSwitcher = o.HideAccountSwitcher; } } } diff --git a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs index b8ed2a230..9a6896428 100644 --- a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs +++ b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs @@ -14,7 +14,7 @@ namespace Bit.App.Pages public class BaseChangePasswordViewModel : BaseViewModel { protected readonly IPlatformUtilsService _platformUtilsService; - protected readonly IUserService _userService; + protected readonly IStateService _stateService; protected readonly IPolicyService _policyService; protected readonly IPasswordGenerationService _passwordGenerationService; protected readonly II18nService _i18nService; @@ -31,7 +31,7 @@ namespace Bit.App.Pages protected BaseChangePasswordViewModel() { _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _policyService = ServiceContainer.Resolve("policyService"); _passwordGenerationService = ServiceContainer.Resolve("passwordGenerationService"); @@ -172,7 +172,7 @@ namespace Bit.App.Pages private async Task> GetPasswordStrengthUserInput() { - var email = await _userService.GetEmailAsync(); + var email = await _stateService.GetEmailAsync(); List userInput = null; var atPosition = email.IndexOf('@'); if (atPosition > -1) diff --git a/src/App/Pages/Accounts/EnvironmentPage.xaml.cs b/src/App/Pages/Accounts/EnvironmentPage.xaml.cs index d6a916131..1f631af71 100644 --- a/src/App/Pages/Accounts/EnvironmentPage.xaml.cs +++ b/src/App/Pages/Accounts/EnvironmentPage.xaml.cs @@ -10,14 +10,11 @@ namespace Bit.App.Pages public partial class EnvironmentPage : BaseContentPage { private readonly IPlatformUtilsService _platformUtilsService; - private readonly IMessagingService _messagingService; private readonly EnvironmentPageViewModel _vm; public EnvironmentPage() { _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _messagingService = ServiceContainer.Resolve("messagingService"); - _messagingService.Send("showStatusBar", true); InitializeComponent(); _vm = BindingContext as EnvironmentPageViewModel; _vm.Page = this; @@ -35,7 +32,6 @@ namespace Bit.App.Pages _vm.SubmitSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync()); _vm.CloseAction = async () => { - _messagingService.Send("showStatusBar", false); await Navigation.PopModalAsync(); }; } diff --git a/src/App/Pages/Accounts/HomePage.xaml b/src/App/Pages/Accounts/HomePage.xaml index 84c727ba6..d118c6078 100644 --- a/src/App/Pages/Accounts/HomePage.xaml +++ b/src/App/Pages/Accounts/HomePage.xaml @@ -6,49 +6,74 @@ xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:u="clr-namespace:Bit.App.Utilities" - xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore" x:DataType="pages:HomeViewModel" Title="{Binding PageTitle}"> - + - + + - - - - - - - - - - - - - - + - + + diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 13bf9f6ec..7c806a82e 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -1,14 +1,13 @@ -using Bit.App.Abstractions; -using Bit.App.Models; -using Bit.App.Resources; -using Bit.Core; -using Bit.Core.Abstractions; -using Bit.Core.Enums; -using Bit.Core.Utilities; -using System; +using System; using System.Linq; using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Controls; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -18,7 +17,7 @@ namespace Bit.App.Pages private readonly IBroadcasterService _broadcasterService; private readonly ISyncService _syncService; private readonly IPushNotificationService _pushNotificationService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IVaultTimeoutService _vaultTimeoutService; private readonly ICipherService _cipherService; private readonly IDeviceActionService _deviceActionService; @@ -37,7 +36,7 @@ namespace Bit.App.Pages _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _syncService = ServiceContainer.Resolve("syncService"); _pushNotificationService = ServiceContainer.Resolve("pushNotificationService"); - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _cipherService = ServiceContainer.Resolve("cipherService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -70,6 +69,10 @@ namespace Bit.App.Pages _absLayout.Children.Remove(_fab); ToolbarItems.Remove(_addItem); } + if (!mainPage) + { + ToolbarItems.Remove(_accountAvatar); + } } protected async override void OnAppearing() @@ -80,6 +83,12 @@ namespace Bit.App.Pages IsBusy = true; } + _accountAvatar?.OnAppearing(); + if (_vm.MainPage) + { + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + } + _broadcasterService.Subscribe(_pageName, async (message) => { if (message.Command == "syncStarted") @@ -100,7 +109,6 @@ namespace Bit.App.Pages } }); - var migratedFromV1 = await _storageService.GetAsync(Constants.MigratedFromV1); await LoadOnAppearedAsync(_mainLayout, false, async () => { if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any()) @@ -123,18 +131,6 @@ namespace Bit.App.Pages await _vm.LoadAsync(); } } - // Forced sync if for some reason we have no data after a v1 migration - if (_vm.MainPage && !_syncService.SyncInProgress && migratedFromV1.GetValueOrDefault() && - !_vm.HasCiphers && - Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None) - { - var triedV1ReSync = await _storageService.GetAsync(Constants.TriedV1Resync); - if (!triedV1ReSync.GetValueOrDefault()) - { - await _storageService.SaveAsync(Constants.TriedV1Resync, true); - await _syncService.FullSyncAsync(true); - } - } await ShowPreviousPageAsync(); AdjustToolbar(); }, _mainContent); @@ -145,14 +141,14 @@ namespace Bit.App.Pages } // Push registration - var lastPushRegistration = await _storageService.GetAsync(Constants.PushLastRegistrationDateKey); + var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync(); lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue); if (Device.RuntimePlatform == Device.iOS) { - var pushPromptShow = await _storageService.GetAsync(Constants.PushInitialPromptShownKey); + var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync(); if (!pushPromptShow.GetValueOrDefault(false)) { - await _storageService.SaveAsync(Constants.PushInitialPromptShownKey, true); + await _stateService.SetPushInitialPromptShownAsync(true); await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert, AppResources.OkGotIt); } @@ -168,30 +164,26 @@ namespace Bit.App.Pages { await _pushNotificationService.RegisterAsync(); } - if (!_deviceActionService.AutofillAccessibilityServiceRunning() - && !_deviceActionService.AutofillServiceEnabled()) - { - if (migratedFromV1.GetValueOrDefault()) - { - var migratedFromV1AutofillPromptShown = await _storageService.GetAsync( - Constants.MigratedFromV1AutofillPromptShown); - if (!migratedFromV1AutofillPromptShown.GetValueOrDefault()) - { - await DisplayAlert(AppResources.Autofill, - AppResources.AutofillServiceNotEnabled, AppResources.Ok); - } - } - } - await _storageService.SaveAsync(Constants.MigratedFromV1AutofillPromptShown, true); } } + protected override bool OnBackButtonPressed() + { + if (_accountListOverlay.IsVisible) + { + _accountListOverlay.HideAsync().FireAndForget(); + return true; + } + return false; + } + protected override void OnDisappearing() { base.OnDisappearing(); IsBusy = false; _broadcasterService.Unsubscribe(_pageName); _vm.DisableRefreshing(); + _accountAvatar?.OnDisappearing(); } private async void RowSelected(object sender, SelectionChangedEventArgs e) @@ -230,6 +222,7 @@ namespace Bit.App.Pages private async void Search_Clicked(object sender, EventArgs e) { + await _accountListOverlay.HideAsync(); if (DoOnce()) { var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null, @@ -240,21 +233,31 @@ namespace Bit.App.Pages private async void Sync_Clicked(object sender, EventArgs e) { + await _accountListOverlay.HideAsync(); await _vm.SyncAsync(); } private async void Lock_Clicked(object sender, EventArgs e) { + await _accountListOverlay.HideAsync(); await _vaultTimeoutService.LockAsync(true, true); } private async void Exit_Clicked(object sender, EventArgs e) { + await _accountListOverlay.HideAsync(); await _vm.ExitAsync(); } private async void AddButton_Clicked(object sender, EventArgs e) { + var skipAction = _accountListOverlay.IsVisible && Device.RuntimePlatform == Device.Android; + await _accountListOverlay.HideAsync(); + if (skipAction) + { + // Account list in the process of closing via tapping on invisible FAB, skip this attempt + return; + } if (!_vm.Deleted && DoOnce()) { var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId); @@ -268,6 +271,7 @@ namespace Bit.App.Pages { return; } + await _accountListOverlay.HideAsync(); if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId)) { await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId))); @@ -284,5 +288,10 @@ namespace Bit.App.Pages _addItem.IsEnabled = !_vm.Deleted; _addItem.IconImageSource = _vm.Deleted ? null : "plus.png"; } + + public async Task HideAccountSwitchingOverlayAsync() + { + await _accountListOverlay.HideAsync(); + } } } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 228c94fb6..20e4ddafd 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -1,16 +1,16 @@ -using Bit.App.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Controls; using Bit.App.Resources; using Bit.App.Utilities; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages @@ -39,13 +39,11 @@ namespace Bit.App.Pages private readonly IFolderService _folderService; private readonly ICollectionService _collectionService; private readonly ISyncService _syncService; - private readonly IUserService _userService; private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IDeviceActionService _deviceActionService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IMessagingService _messagingService; private readonly IStateService _stateService; - private readonly IStorageService _storageService; private readonly IPasswordRepromptService _passwordRepromptService; public GroupingsPageViewModel() @@ -54,13 +52,11 @@ namespace Bit.App.Pages _folderService = ServiceContainer.Resolve("folderService"); _collectionService = ServiceContainer.Resolve("collectionService"); _syncService = ServiceContainer.Resolve("syncService"); - _userService = ServiceContainer.Resolve("userService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _messagingService = ServiceContainer.Resolve("messagingService"); _stateService = ServiceContainer.Resolve("stateService"); - _storageService = ServiceContainer.Resolve("storageService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); Loading = true; @@ -72,6 +68,11 @@ namespace Bit.App.Pages await LoadAsync(); }); CipherOptionsCommand = new Command(CipherOptionsAsync); + + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService) + { + AllowAddAccountRow = true + }; } public bool MainPage { get; set; } @@ -80,7 +81,6 @@ namespace Bit.App.Pages public string CollectionId { get; set; } public Func Filter { get; set; } public bool Deleted { get; set; } - public bool HasCiphers { get; set; } public bool HasFolders { get; set; } public bool HasCollections { get; set; } @@ -139,6 +139,9 @@ namespace Bit.App.Pages get => _websiteIconsEnabled; set => SetProperty(ref _websiteIconsEnabled, value); } + + public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } + public ExtendedObservableCollection GroupedItems { get; set; } public Command RefreshCommand { get; set; } public Command CipherOptionsCommand { get; set; } @@ -150,7 +153,7 @@ namespace Bit.App.Pages { return; } - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; @@ -159,7 +162,7 @@ namespace Bit.App.Pages { return; } - if (await _storageService.GetAsync(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing) + if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) { SyncRefreshing = true; await _syncService.FullSyncAsync(false); @@ -175,8 +178,7 @@ namespace Bit.App.Pages var groupedItems = new List(); var page = Page as GroupingsPage; - WebsiteIconsEnabled = !(await _stateService.GetAsync(Constants.DisableFaviconKey)) - .GetValueOrDefault(); + WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); try { await LoadDataAsync(); diff --git a/src/App/Pages/Vault/SharePageViewModel.cs b/src/App/Pages/Vault/SharePageViewModel.cs index 1f37f088a..0c8d0fd9d 100644 --- a/src/App/Pages/Vault/SharePageViewModel.cs +++ b/src/App/Pages/Vault/SharePageViewModel.cs @@ -16,7 +16,7 @@ namespace Bit.App.Pages private readonly IDeviceActionService _deviceActionService; private readonly ICipherService _cipherService; private readonly ICollectionService _collectionService; - private readonly IUserService _userService; + private readonly IOrganizationService _organizationService; private readonly IPlatformUtilsService _platformUtilsService; private CipherView _cipher; private int _organizationSelectedIndex; @@ -28,7 +28,7 @@ namespace Bit.App.Pages { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _cipherService = ServiceContainer.Resolve("cipherService"); - _userService = ServiceContainer.Resolve("userService"); + _organizationService = ServiceContainer.Resolve("organizationService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _collectionService = ServiceContainer.Resolve("collectionService"); Collections = new ExtendedObservableCollection(); @@ -67,7 +67,7 @@ namespace Bit.App.Pages var allCollections = await _collectionService.GetAllDecryptedAsync(); _writeableCollections = allCollections.Where(c => !c.ReadOnly).ToList(); - var orgs = await _userService.GetAllOrganizationAsync(); + var orgs = await _organizationService.GetAllAsync(); OrganizationOptions = orgs.OrderBy(o => o.Name) .Where(o => o.Enabled && o.Status == OrganizationUserStatusType.Confirmed) .Select(o => new KeyValuePair(o.Name, o.Id)).ToList(); @@ -110,7 +110,7 @@ namespace Bit.App.Pages await _cipherService.ShareWithServerAsync(cipherView, OrganizationId, checkedCollectionIds); await _deviceActionService.HideLoadingAsync(); var movedItemToOrgText = string.Format(AppResources.MovedItemToOrg, cipherView.Name, - (await _userService.GetOrganizationAsync(OrganizationId)).Name); + (await _organizationService.GetAsync(OrganizationId)).Name); _platformUtilsService.ShowToast("success", null, movedItemToOrgText); await Page.Navigation.PopModalAsync(); return true; diff --git a/src/App/Pages/Vault/ViewPageViewModel.cs b/src/App/Pages/Vault/ViewPageViewModel.cs index 3caadcf39..1a73ab69a 100644 --- a/src/App/Pages/Vault/ViewPageViewModel.cs +++ b/src/App/Pages/Vault/ViewPageViewModel.cs @@ -10,10 +10,6 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Bit.Core; using Xamarin.Forms; @@ -23,7 +19,7 @@ namespace Bit.App.Pages { private readonly IDeviceActionService _deviceActionService; private readonly ICipherService _cipherService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ITotpService _totpService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuditService _auditService; @@ -53,7 +49,7 @@ namespace Bit.App.Pages { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _cipherService = ServiceContainer.Resolve("cipherService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _totpService = ServiceContainer.Resolve("totpService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _auditService = ServiceContainer.Resolve("auditService"); @@ -253,7 +249,7 @@ namespace Bit.App.Pages return false; } Cipher = await cipher.DecryptAsync(); - CanAccessPremium = await _userService.CanAccessPremiumAsync(); + CanAccessPremium = await _stateService.CanAccessPremiumAsync(); Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList(); if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) && diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index c790f6f40..af92ac166 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -311,6 +311,30 @@ namespace Bit.App.Resources { } } + public static string RemoveAccount { + get { + return ResourceManager.GetString("RemoveAccount", resourceCulture); + } + } + + public static string RemoveAccountConfirmation { + get { + return ResourceManager.GetString("RemoveAccountConfirmation", resourceCulture); + } + } + + public static string AccountAlreadyAdded { + get { + return ResourceManager.GetString("AccountAlreadyAdded", resourceCulture); + } + } + + public static string SwitchToAlreadyAddedAccountConfirmation { + get { + return ResourceManager.GetString("SwitchToAlreadyAddedAccountConfirmation", resourceCulture); + } + } + public static string MasterPassword { get { return ResourceManager.GetString("MasterPassword", resourceCulture); @@ -3719,6 +3743,36 @@ namespace Bit.App.Resources { } } + public static string AddAccount { + get { + return ResourceManager.GetString("AddAccount", resourceCulture); + } + } + + public static string AccountUnlocked { + get { + return ResourceManager.GetString("AccountUnlocked", resourceCulture); + } + } + + public static string AccountLocked { + get { + return ResourceManager.GetString("AccountLocked", resourceCulture); + } + } + + public static string AccountLoggedOut { + get { + return ResourceManager.GetString("AccountLoggedOut", resourceCulture); + } + } + + public static string AccountSwitchedAutomatically { + get { + return ResourceManager.GetString("AccountSwitchedAutomatically", resourceCulture); + } + } + public static string DeleteAccount { get { return ResourceManager.GetString("DeleteAccount", resourceCulture); diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 369fd5b70..5055b1b80 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -275,6 +275,18 @@ Are you sure you want to log out? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Master Password Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Services/MobileStorageService.cs b/src/App/Services/MobileStorageService.cs index d1fb15cc5..8f506a323 100644 --- a/src/App/Services/MobileStorageService.cs +++ b/src/App/Services/MobileStorageService.cs @@ -13,41 +13,26 @@ namespace Bit.App.Services private readonly HashSet _preferenceStorageKeys = new HashSet { - Constants.VaultTimeoutKey, - Constants.VaultTimeoutActionKey, - Constants.ThemeKey, - Constants.DefaultUriMatch, - Constants.DisableAutoTotpCopyKey, - Constants.DisableFaviconKey, - Constants.ClearClipboardKey, - Constants.AutofillDisableSavePromptKey, - Constants.LastActiveTimeKey, + Constants.StateVersionKey, + Constants.PreAuthEnvironmentUrlsKey, + Constants.AutofillTileAdded, + Constants.AddSitePromptShownKey, Constants.PushInitialPromptShownKey, Constants.LastFileCacheClearKey, Constants.PushLastRegistrationDateKey, Constants.PushRegisteredTokenKey, Constants.PushCurrentTokenKey, Constants.LastBuildKey, - Constants.MigratedFromV1, - Constants.MigratedFromV1AutofillPromptShown, - Constants.TriedV1Resync, Constants.ClearCiphersCacheKey, Constants.BiometricIntegrityKey, Constants.iOSAutoFillClearCiphersCacheKey, Constants.iOSAutoFillBiometricIntegrityKey, Constants.iOSExtensionClearCiphersCacheKey, Constants.iOSExtensionBiometricIntegrityKey, - Constants.EnvironmentUrlsKey, - Constants.InlineAutofillEnabledKey, - Constants.InvalidUnlockAttempts, + Constants.RememberedEmailKey, + Constants.RememberedOrgIdentifierKey, }; - private readonly HashSet _migrateToPreferences = new HashSet - { - Constants.EnvironmentUrlsKey, - }; - private readonly HashSet _haveMigratedToPreferences = new HashSet(); - public MobileStorageService( IStorageService preferenceStorageService, IStorageService liteDbStorageService) @@ -60,24 +45,9 @@ namespace Bit.App.Services { if (_preferenceStorageKeys.Contains(key)) { - var prefValue = await _preferencesStorageService.GetAsync(key); - if (prefValue != null || !_migrateToPreferences.Contains(key) || - _haveMigratedToPreferences.Contains(key)) - { - return prefValue; - } + return await _preferencesStorageService.GetAsync(key); } - var liteDbValue = await _liteDbStorageService.GetAsync(key); - if (_migrateToPreferences.Contains(key)) - { - if (liteDbValue != null) - { - await _preferencesStorageService.SaveAsync(key, liteDbValue); - await _liteDbStorageService.RemoveAsync(key); - } - _haveMigratedToPreferences.Add(key); - } - return liteDbValue; + return await _liteDbStorageService.GetAsync(key); } public Task SaveAsync(string key, T obj) diff --git a/src/App/Services/PushNotificationListenerService.cs b/src/App/Services/PushNotificationListenerService.cs index e0d34e197..9c3154641 100644 --- a/src/App/Services/PushNotificationListenerService.cs +++ b/src/App/Services/PushNotificationListenerService.cs @@ -21,9 +21,8 @@ namespace Bit.App.Services private bool _showNotification; private bool _resolved; - private IStorageService _storageService; private ISyncService _syncService; - private IUserService _userService; + private IStateService _stateService; private IAppIdService _appIdService; private IApiService _apiService; private IMessagingService _messagingService; @@ -64,8 +63,8 @@ namespace Bit.App.Services return; } - var myUserId = await _userService.GetUserIdAsync(); - var isAuthenticated = await _userService.IsAuthenticatedAsync(); + var myUserId = await _stateService.GetActiveUserIdAsync(); + var isAuthenticated = await _stateService.IsAuthenticatedAsync(); switch (notification.Type) { case NotificationType.SyncCipherUpdate: @@ -135,7 +134,7 @@ namespace Bit.App.Services { Resolve(); Debug.WriteLine($"{TAG} - Device Registered - Token : {token}"); - var isAuthenticated = await _userService.IsAuthenticatedAsync(); + var isAuthenticated = await _stateService.IsAuthenticatedAsync(); if (!isAuthenticated) { Debug.WriteLine($"{TAG} - not auth"); @@ -146,7 +145,7 @@ namespace Bit.App.Services try { #if DEBUG - await _storageService.RemoveAsync(Constants.PushInstallationRegistrationError); + await _stateService.SetPushInstallationRegistrationErrorAsync(null); #endif await _apiService.PutDeviceTokenAsync(appId, @@ -154,10 +153,10 @@ namespace Bit.App.Services Debug.WriteLine($"{TAG} Registered device with server."); - await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow); + await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow); if (deviceType == Device.Android) { - await _storageService.SaveAsync(Constants.PushCurrentTokenKey, token); + await _stateService.SetPushCurrentTokenAsync(token); } } #if DEBUG @@ -165,11 +164,11 @@ namespace Bit.App.Services { Debug.WriteLine($"{TAG} Failed to register device."); - await _storageService.SaveAsync(Constants.PushInstallationRegistrationError, apiEx.Error?.Message); + await _stateService.SetPushInstallationRegistrationErrorAsync(apiEx.Error?.Message); } catch (Exception e) { - await _storageService.SaveAsync(Constants.PushInstallationRegistrationError, e.Message); + await _stateService.SetPushInstallationRegistrationErrorAsync(e.Message); throw; } #else @@ -200,9 +199,8 @@ namespace Bit.App.Services { return; } - _storageService = ServiceContainer.Resolve("storageService"); _syncService = ServiceContainer.Resolve("syncService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _appIdService = ServiceContainer.Resolve("appIdService"); _apiService = ServiceContainer.Resolve("apiService"); _messagingService = ServiceContainer.Resolve("messagingService"); diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index bf11e143a..82d8b6ec0 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -2,7 +2,6 @@ using Bit.App.Abstractions; using Bit.App.Pages; using Bit.App.Resources; -using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; @@ -14,6 +13,7 @@ using System.Threading.Tasks; using Bit.App.Models; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Models.Data; using Newtonsoft.Json; using Xamarin.Essentials; using Xamarin.Forms; @@ -46,8 +46,8 @@ namespace Bit.App.Utilities } if (!string.IsNullOrWhiteSpace(cipher.Login.Totp)) { - var userService = ServiceContainer.Resolve("userService"); - var canAccessPremium = await userService.CanAccessPremiumAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + var canAccessPremium = await stateService.CanAccessPremiumAsync(); if (canAccessPremium || cipher.OrganizationUseTotp) { options.Add(AppResources.CopyTotp); @@ -330,33 +330,15 @@ namespace Bit.App.Utilities } public static async Task PerformUpdateTasksAsync(ISyncService syncService, - IDeviceActionService deviceActionService, IStorageService storageService) + IDeviceActionService deviceActionService, IStateService stateService) { var currentBuild = deviceActionService.GetBuildNumber(); - var lastBuild = await storageService.GetAsync(Constants.LastBuildKey); - if (lastBuild == null) - { - // Installed - var currentTimeout = await storageService.GetAsync(Constants.VaultTimeoutKey); - if (currentTimeout == null) - { - await storageService.SaveAsync(Constants.VaultTimeoutKey, 15); - } - - var currentAction = await storageService.GetAsync(Constants.VaultTimeoutActionKey); - if (currentAction == null) - { - await storageService.SaveAsync(Constants.VaultTimeoutActionKey, "lock"); - } - } - else if (lastBuild != currentBuild) + var lastBuild = await stateService.GetLastBuildAsync(); + if (lastBuild == null || lastBuild != currentBuild) { // Updated var tasks = Task.Run(() => syncService.FullSyncAsync(true)); - } - if (lastBuild != currentBuild) - { - await storageService.SaveAsync(Constants.LastBuildKey, currentBuild); + await stateService.SetLastBuildAsync(currentBuild); return true; } return false; @@ -418,35 +400,34 @@ namespace Bit.App.Utilities public static async Task ClearPreviousPage() { - var storageService = ServiceContainer.Resolve("storageService"); - var previousPage = await storageService.GetAsync(Constants.PreviousPageKey); + var stateService = ServiceContainer.Resolve("stateService"); + var previousPage = await stateService.GetPreviousPageInfoAsync(); if (previousPage != null) { - await storageService.RemoveAsync(Constants.PreviousPageKey); + await stateService.SetPreviousPageInfoAsync(null); } return previousPage; } public static async Task IncrementInvalidUnlockAttemptsAsync() { - var storageService = ServiceContainer.Resolve("storageService"); - var invalidUnlockAttempts = await storageService.GetAsync(Constants.InvalidUnlockAttempts); + var stateService = ServiceContainer.Resolve("stateService"); + var invalidUnlockAttempts = await stateService.GetInvalidUnlockAttemptsAsync(); invalidUnlockAttempts++; - await storageService.SaveAsync(Constants.InvalidUnlockAttempts, invalidUnlockAttempts); + await stateService.SetInvalidUnlockAttemptsAsync(invalidUnlockAttempts); return invalidUnlockAttempts; } public static async Task ResetInvalidUnlockAttemptsAsync() { - var storageService = ServiceContainer.Resolve("storageService"); - await storageService.RemoveAsync(Constants.InvalidUnlockAttempts); + var stateService = ServiceContainer.Resolve("stateService"); + await stateService.SetInvalidUnlockAttemptsAsync(null); } public static async Task IsVaultTimeoutImmediateAsync() { - var storageService = ServiceContainer.Resolve("storageService"); - - var vaultTimeoutMinutes = await storageService.GetAsync(Constants.VaultTimeoutKey); + var stateService = ServiceContainer.Resolve("stateService"); + var vaultTimeoutMinutes = await stateService.GetVaultTimeoutAsync(); if (vaultTimeoutMinutes.GetValueOrDefault(-1) == 0) { return true; @@ -466,10 +447,8 @@ namespace Bit.App.Utilities return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped)); } - public static async Task LogOutAsync() + public static async Task LogOutAsync(string userId, bool userInitiated = false) { - var userService = ServiceContainer.Resolve("userService"); - var syncService = ServiceContainer.Resolve("syncService"); var tokenService = ServiceContainer.Resolve("tokenService"); var cryptoService = ServiceContainer.Resolve("cryptoService"); var settingsService = ServiceContainer.Resolve("settingsService"); @@ -481,23 +460,70 @@ namespace Bit.App.Utilities var vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); var stateService = ServiceContainer.Resolve("stateService"); var deviceActionService = ServiceContainer.Resolve("deviceActionService"); + var policyService = ServiceContainer.Resolve("policyService"); var searchService = ServiceContainer.Resolve("searchService"); - var userId = await userService.GetUserIdAsync(); + if (userId == null) + { + userId = await stateService.GetActiveUserIdAsync(); + } + await Task.WhenAll( - syncService.SetLastSyncAsync(DateTime.MinValue), - tokenService.ClearTokenAsync(), - cryptoService.ClearKeysAsync(), - userService.ClearAsync(), - settingsService.ClearAsync(userId), cipherService.ClearAsync(userId), folderService.ClearAsync(userId), collectionService.ClearAsync(userId), - passwordGenerationService.ClearAsync(), - vaultTimeoutService.ClearAsync(), - stateService.PurgeAsync(), + passwordGenerationService.ClearAsync(userId), + deviceActionService.ClearCacheAsync(), + tokenService.ClearTokenAsync(userId), + cryptoService.ClearKeysAsync(userId), + settingsService.ClearAsync(userId), + vaultTimeoutService.ClearAsync(userId), + policyService.ClearAsync(userId), + stateService.LogoutAccountAsync(userId, userInitiated)); + + stateService.BiometricLocked = true; + searchService.ClearIndex(); + + // check if we switched accounts automatically + if (userInitiated && await stateService.GetActiveUserIdAsync() != null) + { + var messagingService = ServiceContainer.Resolve("messagingService"); + messagingService.Send("switchedAccount"); + + var platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + platformUtilsService.ShowToast("info", null, AppResources.AccountSwitchedAutomatically); + } + } + + public static async Task OnAccountSwitchAsync() + { + var environmentService = ServiceContainer.Resolve("environmentService"); + var tokenService = ServiceContainer.Resolve("tokenService"); + var cryptoService = ServiceContainer.Resolve("cryptoService"); + var settingsService = ServiceContainer.Resolve("settingsService"); + var cipherService = ServiceContainer.Resolve("cipherService"); + var folderService = ServiceContainer.Resolve("folderService"); + var collectionService = ServiceContainer.Resolve("collectionService"); + var sendService = ServiceContainer.Resolve("sendService"); + var passwordGenerationService = ServiceContainer.Resolve( + "passwordGenerationService"); + var deviceActionService = ServiceContainer.Resolve("deviceActionService"); + var policyService = ServiceContainer.Resolve("policyService"); + var searchService = ServiceContainer.Resolve("searchService"); + + await environmentService.SetUrlsFromStorageAsync(); + + await Task.WhenAll( + cipherService.ClearCacheAsync(), deviceActionService.ClearCacheAsync()); - vaultTimeoutService.BiometricLocked = true; + tokenService.ClearCache(); + cryptoService.ClearCache(); + settingsService.ClearCache(); + folderService.ClearCache(); + collectionService.ClearCache(); + sendService.ClearCache(); + passwordGenerationService.ClearCache(); + policyService.ClearCache(); searchService.ClearIndex(); } } diff --git a/src/App/Utilities/ThemeManager.cs b/src/App/Utilities/ThemeManager.cs index 9fc24305d..7bc50788b 100644 --- a/src/App/Utilities/ThemeManager.cs +++ b/src/App/Utilities/ThemeManager.cs @@ -1,8 +1,8 @@ using System; using Bit.App.Models; -using Bit.App.Services; using Bit.App.Styles; -using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; using Xamarin.Forms; using System.Linq; using System.Threading.Tasks; @@ -110,16 +110,15 @@ namespace Bit.App.Utilities } } - public static void SetTheme(bool android, ResourceDictionary resources) + public static void SetTheme(ResourceDictionary resources) { - SetThemeStyle(GetTheme(android), resources); + SetThemeStyle(GetTheme(), resources); } - public static string GetTheme(bool android) + public static string GetTheme() { - return Xamarin.Essentials.Preferences.Get( - string.Format(PreferencesStorageService.KeyFormat, Constants.ThemeKey), default(string), - !android ? "group.com.8bit.bitwarden" : default(string)); + var stateService = ServiceContainer.Resolve("stateService"); + return stateService.GetThemeAsync().GetAwaiter().GetResult(); } public static bool OsDarkModeEnabled() diff --git a/src/Core/Abstractions/ICollectionService.cs b/src/Core/Abstractions/ICollectionService.cs index 1f649dba4..21035b421 100644 --- a/src/Core/Abstractions/ICollectionService.cs +++ b/src/Core/Abstractions/ICollectionService.cs @@ -22,4 +22,4 @@ namespace Bit.Core.Abstractions Task UpsertAsync(CollectionData collection); Task UpsertAsync(List collection); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index fc9376e63..c06b5b272 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -9,13 +9,14 @@ namespace Bit.Core.Abstractions { public interface ICryptoService { - Task ClearEncKeyAsync(bool memoryOnly = false); - Task ClearKeyAsync(); - Task ClearKeyHashAsync(); - Task ClearKeyPairAsync(bool memoryOnly = false); - Task ClearKeysAsync(); - Task ClearOrgKeysAsync(bool memoryOnly = false); - Task ClearPinProtectedKeyAsync(); + Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null); + Task ClearKeyAsync(string userId = null); + Task ClearKeyHashAsync(string userId = null); + Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null); + Task ClearKeysAsync(string userId = null); + Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null); + Task ClearPinProtectedKeyAsync(string userId = null); + void ClearCache(); Task DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key); Task DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null); Task DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null); @@ -24,7 +25,7 @@ namespace Bit.Core.Abstractions Task EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task GetEncKeyAsync(SymmetricCryptoKey key = null); Task> GetFingerprintAsync(string userId, byte[] publicKey = null); - Task GetKeyAsync(); + Task GetKeyAsync(string userId = null); Task GetKeyHashAsync(); Task GetOrgKeyAsync(string orgId); Task> GetOrgKeysAsync(); @@ -33,7 +34,7 @@ namespace Bit.Core.Abstractions Task CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key); Task HasEncKeyAsync(); Task HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); - Task HasKeyAsync(); + Task HasKeyAsync(string userId = null); Task> MakeEncKeyAsync(SymmetricCryptoKey key); Task MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations); Task MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations, diff --git a/src/Core/Abstractions/IEventService.cs b/src/Core/Abstractions/IEventService.cs index 0f0a51bf0..6a8f8f42e 100644 --- a/src/Core/Abstractions/IEventService.cs +++ b/src/Core/Abstractions/IEventService.cs @@ -9,4 +9,4 @@ namespace Bit.Core.Abstractions Task CollectAsync(EventType eventType, string cipherId = null, bool uploadImmediately = false); Task UploadEventsAsync(); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IFolderService.cs b/src/Core/Abstractions/IFolderService.cs index 818921fa6..4fe215d1a 100644 --- a/src/Core/Abstractions/IFolderService.cs +++ b/src/Core/Abstractions/IFolderService.cs @@ -23,4 +23,4 @@ namespace Bit.Core.Abstractions Task UpsertAsync(FolderData folder); Task UpsertAsync(List folder); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IOrganizationService.cs b/src/Core/Abstractions/IOrganizationService.cs new file mode 100644 index 000000000..07cab5ac0 --- /dev/null +++ b/src/Core/Abstractions/IOrganizationService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; + +namespace Bit.Core.Abstractions +{ + public interface IOrganizationService + { + Task GetAsync(string id); + Task GetByIdentifierAsync(string identifier); + Task> GetAllAsync(string userId = null); + Task ReplaceAsync(Dictionary organizations); + Task ClearAllAsync(string userId); + } +} diff --git a/src/Core/Abstractions/IPasswordGenerationService.cs b/src/Core/Abstractions/IPasswordGenerationService.cs index 0bdc80b05..4e52f60a3 100644 --- a/src/Core/Abstractions/IPasswordGenerationService.cs +++ b/src/Core/Abstractions/IPasswordGenerationService.cs @@ -8,7 +8,8 @@ namespace Bit.Core.Abstractions public interface IPasswordGenerationService { Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken)); - Task ClearAsync(); + Task ClearAsync(string userId = null); + void ClearCache(); Task GeneratePassphraseAsync(PasswordGenerationOptions options); Task GeneratePasswordAsync(PasswordGenerationOptions options); Task> GetHistoryAsync(); diff --git a/src/Core/Abstractions/IPolicyService.cs b/src/Core/Abstractions/IPolicyService.cs index 78d9cb224..2a2d3504a 100644 --- a/src/Core/Abstractions/IPolicyService.cs +++ b/src/Core/Abstractions/IPolicyService.cs @@ -10,15 +10,15 @@ namespace Bit.Core.Abstractions public interface IPolicyService { void ClearCache(); - Task> GetAll(PolicyType? type); - Task Replace(Dictionary policies); - Task Clear(string userId); - Task GetMasterPasswordPolicyOptions(IEnumerable policies = null); + Task> GetAll(PolicyType? type, string userId = null); + Task Replace(Dictionary policies, string userId = null); + Task ClearAsync(string userId); + Task GetMasterPasswordPolicyOptions(IEnumerable policies = null, string userId = null); Task EvaluateMasterPassword(int passwordStrength, string newPassword, MasterPasswordPolicyOptions enforcedPolicyOptions); Tuple GetResetPasswordPolicyOptions(IEnumerable policies, string orgId); - Task PolicyAppliesToUser(PolicyType policyType, Func policyFilter = null); + Task PolicyAppliesToUser(PolicyType policyType, Func policyFilter = null, string userId = null); int? GetPolicyInt(Policy policy, string key); } } diff --git a/src/Core/Abstractions/ISettingsService.cs b/src/Core/Abstractions/ISettingsService.cs index a5b780e8d..8ad100f38 100644 --- a/src/Core/Abstractions/ISettingsService.cs +++ b/src/Core/Abstractions/ISettingsService.cs @@ -10,4 +10,4 @@ namespace Bit.Core.Abstractions Task>> GetEquivalentDomainsAsync(); Task SetEquivalentDomainsAsync(List> equivalentDomains); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IStateMigrationService.cs b/src/Core/Abstractions/IStateMigrationService.cs new file mode 100644 index 000000000..bdd9164e3 --- /dev/null +++ b/src/Core/Abstractions/IStateMigrationService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Bit.Core.Abstractions +{ + public interface IStateMigrationService + { + Task NeedsMigration(); + Task Migrate(); + } +} diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index 484ed6d52..db65c442a 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -1,12 +1,147 @@ -using System.Threading.Tasks; +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; namespace Bit.Core.Abstractions { public interface IStateService { - Task GetAsync(string key); - Task RemoveAsync(string key); - Task SaveAsync(string key, T obj); - Task PurgeAsync(); + bool BiometricLocked { get; set; } + List AccountViews { get; } + Task GetActiveUserIdAsync(); + Task SetActiveUserAsync(string userId); + Task IsAuthenticatedAsync(string userId = null); + Task GetUserIdAsync(string email); + Task RefreshAccountViewsAsync(bool allowAddAccountRow); + Task AddAccountAsync(Account account); + Task LogoutAccountAsync(string userId, bool userInitiated); + Task GetPreAuthEnvironmentUrlsAsync(); + Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value); + Task GetEnvironmentUrlsAsync(string userId = null); + Task GetBiometricUnlockAsync(string userId = null); + Task SetBiometricUnlockAsync(bool? value, string userId = null); + Task CanAccessPremiumAsync(string userId = null); + Task GetProtectedPinAsync(string userId = null); + Task SetProtectedPinAsync(string value, string userId = null); + Task GetPinProtectedAsync(string userId = null); + Task SetPinProtectedAsync(string value, string userId = null); + Task GetPinProtectedKeyAsync(string userId = null); + Task SetPinProtectedKeyAsync(EncString value, string userId = null); + Task GetKdfTypeAsync(string userId = null); + Task SetKdfTypeAsync(KdfType? value, string userId = null); + Task GetKdfIterationsAsync(string userId = null); + Task SetKdfIterationsAsync(int? value, string userId = null); + Task GetKeyEncryptedAsync(string userId = null); + Task SetKeyEncryptedAsync(string value, string userId = null); + Task GetKeyDecryptedAsync(string userId = null); + Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null); + Task GetKeyHashAsync(string userId = null); + Task SetKeyHashAsync(string value, string userId = null); + Task GetEncKeyEncryptedAsync(string userId = null); + Task SetEncKeyEncryptedAsync(string value, string userId = null); + Task> GetOrgKeysEncryptedAsync(string userId = null); + Task SetOrgKeysEncryptedAsync(Dictionary value, string userId = null); + Task GetPrivateKeyEncryptedAsync(string userId = null); + Task SetPrivateKeyEncryptedAsync(string value, string userId = null); + Task> GetAutofillBlacklistedUrisAsync(string userId = null); + Task SetAutofillBlacklistedUrisAsync(List value, string userId = null); + Task GetAutofillTileAddedAsync(); + Task SetAutofillTileAddedAsync(bool? value); + Task GetEmailAsync(string userId = null); + Task GetNameAsync(string userId = null); + Task GetOrgIdentifierAsync(string userId = null); + Task GetLastActiveTimeAsync(string userId = null); + Task SetLastActiveTimeAsync(long? value, string userId = null); + Task GetVaultTimeoutAsync(string userId = null); + Task SetVaultTimeoutAsync(int? value, string userId = null); + Task GetVaultTimeoutActionAsync(string userId = null); + Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null); + Task GetLastFileCacheClearAsync(string userId = null); + Task SetLastFileCacheClearAsync(DateTime? value, string userId = null); + Task GetPreviousPageInfoAsync(string userId = null); + Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null); + Task GetInvalidUnlockAttemptsAsync(string userId = null); + Task SetInvalidUnlockAttemptsAsync(int? value, string userId = null); + Task GetLastBuildAsync(); + Task SetLastBuildAsync(string value); + Task GetDisableFaviconAsync(string userId = null); + Task SetDisableFaviconAsync(bool? value, string userId = null); + Task GetDisableAutoTotpCopyAsync(string userId = null); + Task SetDisableAutoTotpCopyAsync(bool? value, string userId = null); + Task GetInlineAutofillEnabledAsync(string userId = null); + Task SetInlineAutofillEnabledAsync(bool? value, string userId = null); + Task GetAutofillDisableSavePromptAsync(string userId = null); + Task SetAutofillDisableSavePromptAsync(bool? value, string userId = null); + Task>> GetLocalDataAsync(string userId = null); + Task SetLocalDataAsync(Dictionary> value, string userId = null); + Task> GetEncryptedCiphersAsync(string userId = null); + Task SetEncryptedCiphersAsync(Dictionary value, string userId = null); + Task GetDefaultUriMatchAsync(string userId = null); + Task SetDefaultUriMatchAsync(int? value, string userId = null); + Task> GetNeverDomainsAsync(string userId = null); + Task SetNeverDomainsAsync(HashSet value, string userId = null); + Task GetClearClipboardAsync(string userId = null); + Task SetClearClipboardAsync(int? value, string userId = null); + Task> GetEncryptedCollectionsAsync(string userId = null); + Task SetEncryptedCollectionsAsync(Dictionary value, string userId = null); + Task GetPasswordRepromptAutofillAsync(string userId = null); + Task SetPasswordRepromptAutofillAsync(bool? value, string userId = null); + Task GetPasswordVerifiedAutofillAsync(string userId = null); + Task SetPasswordVerifiedAutofillAsync(bool? value, string userId = null); + Task GetLastSyncAsync(string userId = null); + Task SetLastSyncAsync(DateTime? value, string userId = null); + Task GetSecurityStampAsync(string userId = null); + Task SetSecurityStampAsync(string value, string userId = null); + Task GetEmailVerifiedAsync(string userId = null); + Task SetEmailVerifiedAsync(bool? value, string userId = null); + Task GetSyncOnRefreshAsync(string userId = null); + Task SetSyncOnRefreshAsync(bool? value, string userId = null); + Task GetRememberedEmailAsync(); + Task SetRememberedEmailAsync(string value); + Task GetRememberedOrgIdentifierAsync(); + Task SetRememberedOrgIdentifierAsync(string value); + Task GetThemeAsync(string userId = null); + Task SetThemeAsync(string value, string userId = null); + Task ApplyThemeGloballyAsync(string value); + Task GetAddSitePromptShownAsync(string userId = null); + Task SetAddSitePromptShownAsync(bool? value, string userId = null); + Task GetPushInitialPromptShownAsync(); + Task SetPushInitialPromptShownAsync(bool? value); + Task GetPushLastRegistrationDateAsync(); + Task SetPushLastRegistrationDateAsync(DateTime? value); + Task GetPushInstallationRegistrationErrorAsync(); + Task SetPushInstallationRegistrationErrorAsync(string value); + Task GetPushCurrentTokenAsync(); + Task SetPushCurrentTokenAsync(string value); + Task> GetEventCollectionAsync(); + Task SetEventCollectionAsync(List value); + Task> GetEncryptedFoldersAsync(string userId = null); + Task SetEncryptedFoldersAsync(Dictionary value, string userId = null); + Task> GetEncryptedPoliciesAsync(string userId = null); + Task SetEncryptedPoliciesAsync(Dictionary value, string userId = null); + Task GetPushRegisteredTokenAsync(); + Task SetPushRegisteredTokenAsync(string value); + Task GetUsesKeyConnectorAsync(string userId = null); + Task SetUsesKeyConnectorAsync(bool? value, string userId = null); + Task> GetOrganizationsAsync(string userId = null); + Task SetOrganizationsAsync(Dictionary organizations, string userId = null); + Task GetPasswordGenerationOptionsAsync(string userId = null); + Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, string userId = null); + Task> GetEncryptedPasswordGenerationHistory(string userId = null); + Task SetEncryptedPasswordGenerationHistoryAsync(List value, string userId = null); + Task> GetEncryptedSendsAsync(string userId = null); + Task SetEncryptedSendsAsync(Dictionary value, string userId = null); + Task> GetSettingsAsync(string userId = null); + Task SetSettingsAsync(Dictionary value, string userId = null); + Task GetAccessTokenAsync(string userId = null); + Task SetAccessTokenAsync(string value, bool skipTokenStorage, string userId = null); + Task GetRefreshTokenAsync(string userId = null); + Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null); + Task GetTwoFactorTokenAsync(string email = null); + Task SetTwoFactorTokenAsync(string value, string email = null); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/ITokenService.cs b/src/Core/Abstractions/ITokenService.cs index b08391865..578c97d8f 100644 --- a/src/Core/Abstractions/ITokenService.cs +++ b/src/Core/Abstractions/ITokenService.cs @@ -6,15 +6,16 @@ namespace Bit.Core.Abstractions { public interface ITokenService { - Task ClearTokenAsync(); + Task ClearTokenAsync(string userId = null); Task ClearTwoFactorTokenAsync(string email); + void ClearCache(); JObject DecodeToken(); string GetEmail(); bool GetEmailVerified(); string GetIssuer(); string GetName(); bool GetPremium(); - bool GetIsExternal(); + Task GetIsExternal(); Task GetRefreshTokenAsync(); Task GetTokenAsync(); Task ToggleTokensAsync(); @@ -22,7 +23,7 @@ namespace Bit.Core.Abstractions Task GetTwoFactorTokenAsync(string email); string GetUserId(); Task SetRefreshTokenAsync(string refreshToken); - Task SetTokenAsync(string token); + Task SetAccessTokenAsync(string token, bool forDecodeOnly = false); Task SetTokensAsync(string accessToken, string refreshToken); Task SetTwoFactorTokenAsync(string token, string email); bool TokenNeedsRefresh(int minutes = 5); diff --git a/src/Core/Abstractions/IUserService.cs b/src/Core/Abstractions/IUserService.cs deleted file mode 100644 index d7aaef82b..000000000 --- a/src/Core/Abstractions/IUserService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Bit.Core.Enums; -using Bit.Core.Models.Data; -using Bit.Core.Models.Domain; - -namespace Bit.Core.Abstractions -{ - public interface IUserService - { - Task CanAccessPremiumAsync(); - Task ClearAsync(); - Task ClearOrganizationsAsync(string userId); - Task> GetAllOrganizationAsync(); - Task GetEmailAsync(); - Task GetKdfAsync(); - Task GetKdfIterationsAsync(); - Task GetOrganizationAsync(string id); - Task GetOrganizationByIdentifierAsync(string identifier); - Task GetSecurityStampAsync(); - Task GetEmailVerifiedAsync(); - Task GetForcePasswordReset(); - Task GetUserIdAsync(); - Task IsAuthenticatedAsync(); - Task ReplaceOrganizationsAsync(Dictionary organizations); - Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations); - Task SetSecurityStampAsync(string stamp); - Task SetEmailVerifiedAsync(bool emailVerified); - Task SetForcePasswordReset(bool forcePasswordReset); - } -} diff --git a/src/Core/Abstractions/IVaultTimeoutService.cs b/src/Core/Abstractions/IVaultTimeoutService.cs index bcdb776a8..8fe21b675 100644 --- a/src/Core/Abstractions/IVaultTimeoutService.cs +++ b/src/Core/Abstractions/IVaultTimeoutService.cs @@ -1,23 +1,24 @@ using System; using System.Threading.Tasks; -using Bit.Core.Models.Domain; +using Bit.Core.Enums; namespace Bit.Core.Abstractions { public interface IVaultTimeoutService { - EncString PinProtectedKey { get; set; } - bool BiometricLocked { get; set; } long? DelayLockAndLogoutMs { get; set; } Task CheckVaultTimeoutAsync(); - Task ClearAsync(); - Task IsLockedAsync(); - Task> IsPinLockSetAsync(); - Task IsBiometricLockSetAsync(); - Task LockAsync(bool allowSoftLock = false, bool userInitiated = false); - Task LogOutAsync(); - Task SetVaultTimeoutOptionsAsync(int? timeout, string action); - Task GetVaultTimeout(); + Task ShouldTimeoutAsync(string userId = null); + Task ExecuteTimeoutActionAsync(string userId = null); + Task ClearAsync(string userId = null); + Task IsLockedAsync(string userId = null); + Task IsLoggedOutByTimeoutAsync(string userId = null); + Task> IsPinLockSetAsync(string userId = null); + Task IsBiometricLockSetAsync(string userId = null); + Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); + Task LogOutAsync(bool userInitiated = true, string userId = null); + Task SetVaultTimeoutOptionsAsync(int? timeout, VaultTimeoutAction? action); + Task GetVaultTimeout(string userId = null); } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 1751856aa..119588bdf 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -2,32 +2,20 @@ { public static class Constants { + public const int MaxAccounts = 5; public const string AndroidAppProtocol = "androidapp://"; public const string iOSAppProtocol = "iosapp://"; - public static string SyncOnRefreshKey = "syncOnRefresh"; - public static string VaultTimeoutKey = "lockOption"; - public static string VaultTimeoutActionKey = "vaultTimeoutAction"; - public static string LastActiveTimeKey = "lastActiveTime"; - public static string BiometricUnlockKey = "fingerprintUnlock"; - public static string ProtectedPin = "protectedPin"; - public static string PinProtectedKey = "pinProtectedKey"; - public static string DefaultUriMatch = "defaultUriMatch"; - public static string DisableAutoTotpCopyKey = "disableAutoTotpCopy"; - public static string EnvironmentUrlsKey = "environmentUrls"; + public static string StateVersionKey = "stateVersion"; + public static string StateKey = "state"; + public static string PreAuthEnvironmentUrlsKey = "preAuthEnvironmentUrls"; public static string LastFileCacheClearKey = "lastFileCacheClear"; - public static string AutofillDisableSavePromptKey = "autofillDisableSavePrompt"; - public static string AutofillBlacklistedUrisKey = "autofillBlacklistedUris"; public static string AutofillTileAdded = "autofillTileAdded"; - public static string DisableFaviconKey = "disableFavicon"; public static string PushRegisteredTokenKey = "pushRegisteredToken"; public static string PushCurrentTokenKey = "pushCurrentToken"; public static string PushLastRegistrationDateKey = "pushLastRegistrationDate"; public static string PushInitialPromptShownKey = "pushInitialPromptShown"; - public static string PushInstallationRegistrationError = "pushInstallationRegistrationError"; - public static string ThemeKey = "theme"; - public static string ClearClipboardKey = "clearClipboard"; + public static string PushInstallationRegistrationErrorKey = "pushInstallationRegistrationError"; public static string LastBuildKey = "lastBuild"; - public static string OldUserIdKey = "userId"; public static string AddSitePromptShownKey = "addSitePromptShown"; public static string ClearCiphersCacheKey = "clearCiphersCache"; public static string BiometricIntegrityKey = "biometricIntegrityState"; @@ -35,15 +23,9 @@ public static string iOSAutoFillBiometricIntegrityKey = "iOSAutoFillBiometricIntegrityState"; public static string iOSExtensionClearCiphersCacheKey = "iOSExtensionClearCiphersCache"; public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState"; - public static string MigratedFromV1 = "migratedFromV1"; - public static string MigratedFromV1AutofillPromptShown = "migratedV1AutofillPromptShown"; - public static string TriedV1Resync = "triedV1Resync"; public static string EventCollectionKey = "eventCollection"; - public static string PreviousPageKey = "previousPage"; - public static string InlineAutofillEnabledKey = "inlineAutofillEnabled"; - public static string InvalidUnlockAttempts = "invalidUnlockAttempts"; - public static string PasswordRepromptAutofillKey = "passwordRepromptAutofillKey"; - public static string PasswordVerifiedAutofillKey = "passwordVerifiedAutofillKey"; + public static string RememberedEmailKey = "rememberedEmail"; + public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier"; public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; public const int SaveFileRequestCode = 44; @@ -59,5 +41,42 @@ iOSAutoFillClearCiphersCacheKey, iOSExtensionClearCiphersCacheKey }; + + public static string CiphersKey(string userId) => $"ciphers_{userId}"; + public static string FoldersKey(string userId) => $"folders_{userId}"; + public static string CollectionsKey(string userId) => $"collections_{userId}"; + public static string OrganizationsKey(string userId) => $"organizations_{userId}"; + public static string LocalDataKey(string userId) => $"ciphersLocalData_{userId}"; + public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}"; + public static string SendsKey(string userId) => $"sends_{userId}"; + public static string PoliciesKey(string userId) => $"policies_{userId}"; + public static string KeyKey(string userId) => $"key_{userId}"; + public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}"; + public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}"; + public static string EncKeyKey(string userId) => $"encKey_{userId}"; + public static string KeyHashKey(string userId) => $"keyHash_{userId}"; + public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}"; + public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}"; + public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}"; + public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}"; + public static string LastActiveTimeKey(string userId) => $"lastActiveTime_{userId}"; + public static string InvalidUnlockAttemptsKey(string userId) => $"invalidUnlockAttempts_{userId}"; + public static string InlineAutofillEnabledKey(string userId) => $"inlineAutofillEnabled_{userId}"; + public static string AutofillDisableSavePromptKey(string userId) => $"autofillDisableSavePrompt_{userId}"; + public static string AutofillBlacklistedUrisKey(string userId) => $"autofillBlacklistedUris_{userId}"; + public static string ClearClipboardKey(string userId) => $"clearClipboard_{userId}"; + public static string SyncOnRefreshKey(string userId) => $"syncOnRefresh_{userId}"; + public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}"; + public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}"; + public static string ThemeKey(string userId) => $"theme_{userId}"; + public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}"; + public static string PreviousPageKey(string userId) => $"previousPage_{userId}"; + public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}"; + public static string PasswordVerifiedAutofillKey(string userId) => $"passwordVerifiedAutofillKey_{userId}"; + public static string SettingsKey(string userId) => $"settings_{userId}"; + public static string UsesKeyConnectorKey(string userId) => $"usesKeyConnector_{userId}"; + public static string ProtectedPinKey(string userId) => $"protectedPin_{userId}"; + public static string LastSyncKey(string userId) => $"lastSync_{userId}"; + public static string BiometricUnlockKey(string userId) => $"biometricUnlock_{userId}"; } } diff --git a/src/Core/Enums/AuthenticationStatus.cs b/src/Core/Enums/AuthenticationStatus.cs new file mode 100644 index 000000000..2d81e071d --- /dev/null +++ b/src/Core/Enums/AuthenticationStatus.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Enums +{ + public enum AuthenticationStatus : byte + { + LoggedOut = 0, + Locked = 1, + Unlocked = 2, + } +} diff --git a/src/Core/Enums/StorageLocation.cs b/src/Core/Enums/StorageLocation.cs new file mode 100644 index 000000000..faa9476c4 --- /dev/null +++ b/src/Core/Enums/StorageLocation.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Enums +{ + public enum StorageLocation + { + Both = 0, + Disk = 1, + Memory = 2 + } +} diff --git a/src/Core/Enums/VaultTimeoutAction.cs b/src/Core/Enums/VaultTimeoutAction.cs new file mode 100644 index 000000000..5a9c33909 --- /dev/null +++ b/src/Core/Enums/VaultTimeoutAction.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Enums +{ + public enum VaultTimeoutAction + { + Lock = 0, + Logout = 1, + } +} diff --git a/src/App/Models/PreviousPageInfo.cs b/src/Core/Models/Data/PreviousPageInfo.cs similarity index 86% rename from src/App/Models/PreviousPageInfo.cs rename to src/Core/Models/Data/PreviousPageInfo.cs index 3f6bba7de..bcf8e29c3 100644 --- a/src/App/Models/PreviousPageInfo.cs +++ b/src/Core/Models/Data/PreviousPageInfo.cs @@ -1,4 +1,4 @@ -namespace Bit.App.Models +namespace Bit.Core.Models.Data { public class PreviousPageInfo { diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs new file mode 100644 index 000000000..43360c9cd --- /dev/null +++ b/src/Core/Models/Domain/Account.cs @@ -0,0 +1,110 @@ +using Bit.Core.Enums; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Domain +{ + public class Account : Domain + { + public AccountProfile Profile; + public AccountTokens Tokens; + public AccountSettings Settings; + public AccountKeys Keys; + + public Account() { } + + public Account(AccountProfile profile, AccountTokens tokens) + { + Profile = profile; + Tokens = tokens; + Settings = new AccountSettings(); + Keys = new AccountKeys(); + } + + public Account(Account account) + { + // Copy constructor excludes Keys (for storage) + Profile = new AccountProfile(account.Profile); + Tokens = new AccountTokens(account.Tokens); + Settings = new AccountSettings(account.Settings); + } + + public class AccountProfile + { + public AccountProfile() { } + + public AccountProfile(AccountProfile copy) + { + if (copy == null) + { + return; + } + + UserId = copy.UserId; + Email = copy.Email; + Name = copy.Name; + Stamp = copy.Stamp; + OrgIdentifier = copy.OrgIdentifier; + KdfType = copy.KdfType; + KdfIterations = copy.KdfIterations; + EmailVerified = copy.EmailVerified; + HasPremiumPersonally = copy.HasPremiumPersonally; + } + + public string UserId; + public string Email; + public string Name; + public string Stamp; + public string OrgIdentifier; + public KdfType? KdfType; + public int? KdfIterations; + public bool? EmailVerified; + public bool? HasPremiumPersonally; + } + + public class AccountTokens + { + public AccountTokens() { } + + public AccountTokens(AccountTokens copy) + { + if (copy == null) + { + return; + } + + AccessToken = copy.AccessToken; + RefreshToken = copy.RefreshToken; + } + + public string AccessToken; + public string RefreshToken; + } + + public class AccountSettings + { + public AccountSettings() { } + + public AccountSettings(AccountSettings copy) + { + if (copy == null) + { + return; + } + + EnvironmentUrls = copy.EnvironmentUrls; + VaultTimeout = copy.VaultTimeout; + VaultTimeoutAction = copy.VaultTimeoutAction; + } + + public EnvironmentUrlData EnvironmentUrls; + public int? VaultTimeout; + public VaultTimeoutAction? VaultTimeoutAction; + } + + public class AccountKeys + { + public SymmetricCryptoKey Key; + public EncString PinProtectedKey; + } + } +} diff --git a/src/Core/Models/Domain/State.cs b/src/Core/Models/Domain/State.cs new file mode 100644 index 000000000..f8180fab8 --- /dev/null +++ b/src/Core/Models/Domain/State.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Bit.Core.Models.Domain +{ + public class State : Domain + { + public Dictionary Accounts { get; set; } + public string ActiveUserId { get; set; } + } +} diff --git a/src/Core/Models/Domain/StorageOptions.cs b/src/Core/Models/Domain/StorageOptions.cs new file mode 100644 index 000000000..0d9061876 --- /dev/null +++ b/src/Core/Models/Domain/StorageOptions.cs @@ -0,0 +1,13 @@ +using Bit.Core.Enums; + +namespace Bit.Core.Models.Domain +{ + public class StorageOptions : Domain + { + public StorageLocation? StorageLocation { get; set; } + public bool? UseSecureStorage { get; set; } + public string UserId { get; set; } + public string Email { get; set; } + public bool? SkipTokenStorage { get; set; } + } +} diff --git a/src/Core/Models/View/AccountView.cs b/src/Core/Models/View/AccountView.cs new file mode 100644 index 000000000..e89afc511 --- /dev/null +++ b/src/Core/Models/View/AccountView.cs @@ -0,0 +1,41 @@ +using Bit.Core.Enums; +using Bit.Core.Models.Domain; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.View +{ + public class AccountView : View + { + public AccountView() { } + + public AccountView(Account a = null, bool isActive = false) + { + if (a == null) + { + // null will render as "Add Account" row + return; + } + IsAccount = true; + IsActive = isActive; + UserId = a.Profile?.UserId; + Email = a.Profile?.Email; + Name = a.Profile?.Name; + if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.WebVault)) + { + Hostname = CoreHelpers.GetHostname(a.Settings?.EnvironmentUrls?.WebVault); + } + else if (!string.IsNullOrWhiteSpace(a.Settings?.EnvironmentUrls?.Base)) + { + Hostname = CoreHelpers.GetHostname(a.Settings?.EnvironmentUrls?.Base); + } + } + + public bool IsAccount { get; set; } + public AuthenticationStatus? AuthStatus { get; set; } + public bool IsActive { get; set; } + public string UserId { get; set; } + public string Email { get; set; } + public string Name { get; set; } + public string Hostname { get; set; } + } +} diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index c0b11a6a1..78853c233 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -25,12 +25,12 @@ namespace Bit.Core.Services private readonly HttpClient _httpClient = new HttpClient(); private readonly ITokenService _tokenService; private readonly IPlatformUtilsService _platformUtilsService; - private readonly Func _logoutCallbackAsync; + private readonly Func, Task> _logoutCallbackAsync; public ApiService( ITokenService tokenService, IPlatformUtilsService platformUtilsService, - Func logoutCallbackAsync, + Func, Task> logoutCallbackAsync, string customUserAgent = null) { _tokenService = tokenService; @@ -709,7 +709,7 @@ namespace Bit.Core.Services response.StatusCode == HttpStatusCode.Forbidden )) { - await _logoutCallbackAsync(true); + await _logoutCallbackAsync(new Tuple(null, false, true)); return null; } try diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index ca76c437f..f919f2766 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -14,13 +14,12 @@ namespace Bit.Core.Services private readonly ICryptoService _cryptoService; private readonly ICryptoFunctionService _cryptoFunctionService; private readonly IApiService _apiService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ITokenService _tokenService; private readonly IAppIdService _appIdService; private readonly II18nService _i18nService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IMessagingService _messagingService; - private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IKeyConnectorService _keyConnectorService; private readonly bool _setCryptoKeys; @@ -30,7 +29,7 @@ namespace Bit.Core.Services ICryptoService cryptoService, ICryptoFunctionService cryptoFunctionService, IApiService apiService, - IUserService userService, + IStateService stateService, ITokenService tokenService, IAppIdService appIdService, II18nService i18nService, @@ -43,13 +42,12 @@ namespace Bit.Core.Services _cryptoService = cryptoService; _cryptoFunctionService = cryptoFunctionService; _apiService = apiService; - _userService = userService; + _stateService = stateService; _tokenService = tokenService; _appIdService = appIdService; _i18nService = i18nService; _platformUtilsService = platformUtilsService; _messagingService = messagingService; - _vaultTimeoutService = vaultTimeoutService; _keyConnectorService = keyConnectorService; _setCryptoKeys = setCryptoKeys; @@ -354,9 +352,26 @@ namespace Bit.Core.Services { await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email); } - await _tokenService.SetTokensAsync(tokenResponse.AccessToken, tokenResponse.RefreshToken); - await _userService.SetInformationAsync(_tokenService.GetUserId(), _tokenService.GetEmail(), - tokenResponse.Kdf, tokenResponse.KdfIterations); + await _tokenService.SetAccessTokenAsync(tokenResponse.AccessToken, true); + await _stateService.AddAccountAsync( + new Account( + new Account.AccountProfile() + { + UserId = _tokenService.GetUserId(), + Email = _tokenService.GetEmail(), + Name = _tokenService.GetName(), + KdfType = tokenResponse.Kdf, + KdfIterations = tokenResponse.KdfIterations, + HasPremiumPersonally = _tokenService.GetPremium(), + }, + new Account.AccountTokens() + { + AccessToken = tokenResponse.AccessToken, + RefreshToken = tokenResponse.RefreshToken, + } + ) + ); + _messagingService.Send("accountAdded"); if (_setCryptoKeys) { if (key != null) @@ -430,7 +445,7 @@ namespace Bit.Core.Services } - _vaultTimeoutService.BiometricLocked = false; + _stateService.BiometricLocked = false; _messagingService.Send("loggedIn"); return result; } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index 5f644472d..e39f5cb6d 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -19,15 +19,11 @@ namespace Bit.Core.Services { public class CipherService : ICipherService { - private const string Keys_CiphersFormat = "ciphers_{0}"; - private const string Keys_LocalData = "ciphersLocalData"; - private const string Keys_NeverDomains = "neverDomains"; - private readonly string[] _ignoredSearchTerms = new string[] { "com", "net", "org", "android", "io", "co", "uk", "au", "nz", "fr", "de", "tv", "info", "app", "apps", "eu", "me", "dev", "jp", "mobile" }; private List _decryptedCipherCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ISettingsService _settingsService; private readonly IApiService _apiService; private readonly IFileUploadService _fileUploadService; @@ -45,7 +41,7 @@ namespace Bit.Core.Services public CipherService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, ISettingsService settingsService, IApiService apiService, IFileUploadService fileUploadService, @@ -56,7 +52,7 @@ namespace Bit.Core.Services string[] allClearCipherCacheKeys) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _settingsService = settingsService; _apiService = apiService; _fileUploadService = fileUploadService; @@ -211,11 +207,8 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var localData = await _storageService.GetAsync>>( - Keys_LocalData); - var ciphers = await _storageService.GetAsync>( - string.Format(Keys_CiphersFormat, userId)); + var localData = await _stateService.GetLocalDataAsync(); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (!ciphers?.ContainsKey(id) ?? true) { return null; @@ -226,11 +219,8 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var localData = await _storageService.GetAsync>>( - Keys_LocalData); - var ciphers = await _storageService.GetAsync>( - string.Format(Keys_CiphersFormat, userId)); + var localData = await _stateService.GetLocalDataAsync(); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); var response = ciphers?.Select(c => new Cipher(c.Value, false, localData?.ContainsKey(c.Key) ?? false ? localData[c.Key] : null)); return response?.ToList() ?? new List(); @@ -347,7 +337,7 @@ namespace Bit.Core.Services var others = new List(); var ciphers = await ciphersTask; - var defaultMatch = (UriMatchType?)(await _storageService.GetAsync(Constants.DefaultUriMatch)); + var defaultMatch = (UriMatchType?)(await _stateService.GetDefaultUriMatchAsync()); if (defaultMatch == null) { defaultMatch = UriMatchType.Domain; @@ -457,8 +447,7 @@ namespace Bit.Core.Services public async Task UpdateLastUsedDateAsync(string id) { - var ciphersLocalData = await _storageService.GetAsync>>( - Keys_LocalData); + var ciphersLocalData = await _stateService.GetLocalDataAsync(); if (ciphersLocalData == null) { ciphersLocalData = new Dictionary>(); @@ -476,7 +465,7 @@ namespace Bit.Core.Services ciphersLocalData[id].Add("lastUsedDate", DateTime.UtcNow); } - await _storageService.SaveAsync(Keys_LocalData, ciphersLocalData); + await _stateService.SetLocalDataAsync(ciphersLocalData); // Update cache if (DecryptedCipherCache == null) { @@ -495,13 +484,13 @@ namespace Bit.Core.Services { return; } - var domains = await _storageService.GetAsync>(Keys_NeverDomains); + var domains = await _stateService.GetNeverDomainsAsync(); if (domains == null) { domains = new HashSet(); } domains.Add(domain); - await _storageService.SaveAsync(Keys_NeverDomains, domains); + await _stateService.SetNeverDomainsAsync(domains); } public async Task SaveWithServerAsync(Cipher cipher) @@ -526,7 +515,7 @@ namespace Bit.Core.Services var request = new CipherRequest(cipher); response = await _apiService.PutCipherAsync(cipher.Id, request); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = new CipherData(response, userId, cipher.CollectionIds); await UpsertAsync(data); } @@ -550,7 +539,7 @@ namespace Bit.Core.Services var encCipher = await EncryptAsync(cipher); var request = new CipherShareRequest(encCipher); var response = await _apiService.PutShareCipherAsync(cipher.Id, request); - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = new CipherData(response, userId, collectionIds); await UpsertAsync(data); } @@ -581,7 +570,7 @@ namespace Bit.Core.Services response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var cData = new CipherData(response, userId, cipher.CollectionIds); await UpsertAsync(cData); return new Cipher(cData); @@ -602,16 +591,14 @@ namespace Bit.Core.Services { var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList()); await _apiService.PutCipherCollectionsAsync(cipher.Id, request); - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = cipher.ToCipherData(userId); await UpsertAsync(data); } public async Task UpsertAsync(CipherData cipher) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(storageKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { ciphers = new Dictionary(); @@ -621,15 +608,13 @@ namespace Bit.Core.Services ciphers.Add(cipher.Id, null); } ciphers[cipher.Id] = cipher; - await _storageService.SaveAsync(storageKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task UpsertAsync(List cipher) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(storageKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { ciphers = new Dictionary(); @@ -642,28 +627,25 @@ namespace Bit.Core.Services } ciphers[c.Id] = c; } - await _storageService.SaveAsync(storageKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task ReplaceAsync(Dictionary ciphers) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_CiphersFormat, userId), ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_CiphersFormat, userId)); + await _stateService.SetEncryptedCiphersAsync(null, userId); await ClearCacheAsync(); } public async Task DeleteAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -673,15 +655,13 @@ namespace Bit.Core.Services return; } ciphers.Remove(id); - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task DeleteAsync(List ids) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -694,7 +674,7 @@ namespace Bit.Core.Services } ciphers.Remove(id); } - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } @@ -706,9 +686,7 @@ namespace Bit.Core.Services public async Task DeleteAttachmentAsync(string id, string attachmentId) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null || !ciphers.ContainsKey(id) || ciphers[id].Attachments == null) { return; @@ -718,7 +696,7 @@ namespace Bit.Core.Services { ciphers[id].Attachments.Remove(attachment); } - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } @@ -771,9 +749,7 @@ namespace Bit.Core.Services public async Task SoftDeleteWithServerAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -785,15 +761,13 @@ namespace Bit.Core.Services await _apiService.PutDeleteCipherAsync(id); ciphers[id].DeletedDate = DateTime.UtcNow; - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } public async Task RestoreWithServerAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var cipherKey = string.Format(Keys_CiphersFormat, userId); - var ciphers = await _storageService.GetAsync>(cipherKey); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers == null) { return; @@ -805,7 +779,7 @@ namespace Bit.Core.Services var response = await _apiService.PutRestoreCipherAsync(id); ciphers[id].DeletedDate = null; ciphers[id].RevisionDate = response.RevisionDate; - await _storageService.SaveAsync(cipherKey, ciphers); + await _stateService.SetEncryptedCiphersAsync(ciphers); await ClearCacheAsync(); } diff --git a/src/Core/Services/CollectionService.cs b/src/Core/Services/CollectionService.cs index aadce803e..ddd7cfb12 100644 --- a/src/Core/Services/CollectionService.cs +++ b/src/Core/Services/CollectionService.cs @@ -13,24 +13,20 @@ namespace Bit.Core.Services { public class CollectionService : ICollectionService { - private const string Keys_CollectionsFormat = "collections_{0}"; private const char NestingDelimiter = '/'; private List _decryptedCollectionCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly II18nService _i18nService; public CollectionService( ICryptoService cryptoService, - IUserService userService, - IStorageService storageService, + IStateService stateService, II18nService i18nService) { _cryptoService = cryptoService; - _userService = userService; - _storageService = storageService; + _stateService = stateService; _i18nService = i18nService; } @@ -83,9 +79,7 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var collections = await _storageService.GetAsync>( - string.Format(Keys_CollectionsFormat, userId)); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (!collections?.ContainsKey(id) ?? true) { return null; @@ -95,9 +89,7 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var collections = await _storageService.GetAsync>( - string.Format(Keys_CollectionsFormat, userId)); + var collections = await _stateService.GetEncryptedCollectionsAsync(); var response = collections?.Select(c => new Collection(c.Value)); return response?.ToList() ?? new List(); } @@ -148,9 +140,7 @@ namespace Bit.Core.Services public async Task UpsertAsync(CollectionData collection) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CollectionsFormat, userId); - var collections = await _storageService.GetAsync>(storageKey); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null) { collections = new Dictionary(); @@ -160,15 +150,13 @@ namespace Bit.Core.Services collections.Add(collection.Id, null); } collections[collection.Id] = collection; - await _storageService.SaveAsync(storageKey, collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task UpsertAsync(List collection) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_CollectionsFormat, userId); - var collections = await _storageService.GetAsync>(storageKey); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null) { collections = new Dictionary(); @@ -181,34 +169,31 @@ namespace Bit.Core.Services } collections[c.Id] = c; } - await _storageService.SaveAsync(storageKey, collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task ReplaceAsync(Dictionary collections) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_CollectionsFormat, userId), collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_CollectionsFormat, userId)); + await _stateService.SetEncryptedCollectionsAsync(null, userId); _decryptedCollectionCache = null; } public async Task DeleteAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var collectionKey = string.Format(Keys_CollectionsFormat, userId); - var collections = await _storageService.GetAsync>(collectionKey); + var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null || !collections.ContainsKey(id)) { return; } collections.Remove(id); - await _storageService.SaveAsync(collectionKey, collections); + await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 375f6d467..3b3318be7 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -14,11 +14,9 @@ namespace Bit.Core.Services { public class CryptoService : ICryptoService { - private readonly IStorageService _storageService; - private readonly IStorageService _secureStorageService; + private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; - private SymmetricCryptoKey _key; private SymmetricCryptoKey _encKey; private SymmetricCryptoKey _legacyEtmKey; private string _keyHash; @@ -28,39 +26,31 @@ namespace Bit.Core.Services private Task _getEncKeysTask; private Task> _getOrgKeysTask; - private const string Keys_Key = "key"; - private const string Keys_EncOrgKeys = "encOrgKeys"; - private const string Keys_EncPrivateKey = "encPrivateKey"; - private const string Keys_EncKey = "encKey"; - private const string Keys_KeyHash = "keyHash"; - public CryptoService( - IStorageService storageService, - IStorageService secureStorageService, + IStateService stateService, ICryptoFunctionService cryptoFunctionService) { - _storageService = storageService; - _secureStorageService = secureStorageService; + _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; } public async Task SetKeyAsync(SymmetricCryptoKey key) { - _key = key; - var option = await _storageService.GetAsync(Constants.VaultTimeoutKey); - var biometric = await _storageService.GetAsync(Constants.BiometricUnlockKey); + await _stateService.SetKeyDecryptedAsync(key); + var option = await _stateService.GetVaultTimeoutAsync(); + var biometric = await _stateService.GetBiometricUnlockAsync(); if (option.HasValue && !biometric.GetValueOrDefault()) { // If we have a lock option set, we do not store the key return; } - await _secureStorageService.SaveAsync(Keys_Key, key?.KeyB64); + await _stateService.SetKeyEncryptedAsync(key?.KeyB64); } public async Task SetKeyHashAsync(string keyHash) { _keyHash = keyHash; - await _storageService.SaveAsync(Keys_KeyHash, keyHash); + await _stateService.SetKeyHashAsync(keyHash); } public async Task SetEncKeyAsync(string encKey) @@ -69,7 +59,7 @@ namespace Bit.Core.Services { return; } - await _storageService.SaveAsync(Keys_EncKey, encKey); + await _stateService.SetEncKeyEncryptedAsync(encKey); _encKey = null; } @@ -79,7 +69,7 @@ namespace Bit.Core.Services { return; } - await _storageService.SaveAsync(Keys_EncPrivateKey, encPrivateKey); + await _stateService.SetPrivateKeyEncryptedAsync(encPrivateKey); _privateKey = null; } @@ -87,21 +77,23 @@ namespace Bit.Core.Services { var orgKeys = orgs.ToDictionary(org => org.Id, org => org.Key); _orgKeys = null; - await _storageService.SaveAsync(Keys_EncOrgKeys, orgKeys); + await _stateService.SetOrgKeysEncryptedAsync(orgKeys); } - public async Task GetKeyAsync() + public async Task GetKeyAsync(string userId = null) { - if (_key != null) + var inMemoryKey = await _stateService.GetKeyDecryptedAsync(userId); + if (inMemoryKey != null) { - return _key; + return inMemoryKey; } - var key = await _secureStorageService.GetAsync(Keys_Key); + var key = await _stateService.GetKeyEncryptedAsync(userId); if (key != null) { - _key = new SymmetricCryptoKey(Convert.FromBase64String(key)); + inMemoryKey = new SymmetricCryptoKey(Convert.FromBase64String(key)); + await _stateService.SetKeyDecryptedAsync(inMemoryKey, userId); } - return _key; + return inMemoryKey; } public async Task GetKeyHashAsync() @@ -110,7 +102,7 @@ namespace Bit.Core.Services { return _keyHash; } - var keyHash = await _storageService.GetAsync(Keys_KeyHash); + var keyHash = await _stateService.GetKeyHashAsync(); if (keyHash != null) { _keyHash = keyHash; @@ -132,7 +124,7 @@ namespace Bit.Core.Services { try { - var encKey = await _storageService.GetAsync(Keys_EncKey); + var encKey = await _stateService.GetEncKeyEncryptedAsync(); if (encKey == null) { return null; @@ -200,7 +192,7 @@ namespace Bit.Core.Services { return _privateKey; } - var encPrivateKey = await _storageService.GetAsync(Keys_EncPrivateKey); + var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(); if (encPrivateKey == null) { return null; @@ -238,7 +230,7 @@ namespace Bit.Core.Services { try { - var encOrgKeys = await _storageService.GetAsync>(Keys_EncOrgKeys); + var encOrgKeys = await _stateService.GetOrgKeysEncryptedAsync(); if (encOrgKeys == null) { return null; @@ -303,84 +295,95 @@ namespace Bit.Core.Services return false; } - public async Task HasKeyAsync() + public async Task HasKeyAsync(string userId = null) { - var key = await GetKeyAsync(); + var key = await GetKeyAsync(userId); return key != null; } public async Task HasEncKeyAsync() { - var encKey = await _storageService.GetAsync(Keys_EncKey); + var encKey = await _stateService.GetEncKeyEncryptedAsync(); return encKey != null; } - public async Task ClearKeyAsync() + public async Task ClearKeyAsync(string userId = null) { - _key = _legacyEtmKey = null; - await _secureStorageService.RemoveAsync(Keys_Key); + await _stateService.SetKeyDecryptedAsync(null, userId); + _legacyEtmKey = null; + await _stateService.SetKeyEncryptedAsync(null, userId); } - public async Task ClearKeyHashAsync() + public async Task ClearKeyHashAsync(string userId = null) { _keyHash = null; - await _storageService.RemoveAsync(Keys_KeyHash); + await _stateService.SetKeyHashAsync(null, userId); } - public async Task ClearEncKeyAsync(bool memoryOnly = false) + public async Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null) { _encKey = null; if (!memoryOnly) { - await _storageService.RemoveAsync(Keys_EncKey); + await _stateService.SetEncKeyEncryptedAsync(null, userId); } } - public async Task ClearKeyPairAsync(bool memoryOnly = false) + public async Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null) { _publicKey = _privateKey = null; if (!memoryOnly) { - await _storageService.RemoveAsync(Keys_EncPrivateKey); + await _stateService.SetPrivateKeyEncryptedAsync(null, userId); } } - public async Task ClearOrgKeysAsync(bool memoryOnly = false) + public async Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null) { _orgKeys = null; if (!memoryOnly) { - await _storageService.RemoveAsync(Keys_EncOrgKeys); + await _stateService.SetOrgKeysEncryptedAsync(null, userId); } } - public async Task ClearPinProtectedKeyAsync() + public async Task ClearPinProtectedKeyAsync(string userId = null) { - await _storageService.RemoveAsync(Constants.PinProtectedKey); + await _stateService.SetPinProtectedAsync(null, userId); } - public async Task ClearKeysAsync() + public void ClearCache() + { + _encKey = null; + _legacyEtmKey = null; + _keyHash = null; + _publicKey = null; + _privateKey = null; + _orgKeys = null; + } + + public async Task ClearKeysAsync(string userId = null) { await Task.WhenAll(new Task[] { - ClearKeyAsync(), - ClearKeyHashAsync(), - ClearOrgKeysAsync(), - ClearEncKeyAsync(), - ClearKeyPairAsync(), - ClearPinProtectedKeyAsync() + ClearKeyAsync(userId), + ClearKeyHashAsync(userId), + ClearOrgKeysAsync(false, userId), + ClearEncKeyAsync(false, userId), + ClearKeyPairAsync(false, userId), + ClearPinProtectedKeyAsync(userId) }); } public async Task ToggleKeyAsync() { var key = await GetKeyAsync(); - var option = await _storageService.GetAsync(Constants.VaultTimeoutKey); - var biometric = await _storageService.GetAsync(Constants.BiometricUnlockKey); + var option = await _stateService.GetVaultTimeoutAsync(); + var biometric = await _stateService.GetBiometricUnlockAsync(); if (!biometric.GetValueOrDefault() && (option != null || option == 0)) { await ClearKeyAsync(); - _key = key; + await _stateService.SetKeyDecryptedAsync(key); return; } await SetKeyAsync(key); @@ -415,7 +418,7 @@ namespace Bit.Core.Services { if (protectedKeyCs == null) { - var pinProtectedKey = await _storageService.GetAsync(Constants.PinProtectedKey); + var pinProtectedKey = await _stateService.GetPinProtectedAsync(); if (pinProtectedKey == null) { throw new Exception("No PIN protected key found."); diff --git a/src/Core/Services/EnvironmentService.cs b/src/Core/Services/EnvironmentService.cs index 315e27126..b5a6e4511 100644 --- a/src/Core/Services/EnvironmentService.cs +++ b/src/Core/Services/EnvironmentService.cs @@ -9,14 +9,14 @@ namespace Bit.Core.Services public class EnvironmentService : IEnvironmentService { private readonly IApiService _apiService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; public EnvironmentService( IApiService apiService, - IStorageService storageService) + IStateService stateService) { _apiService = apiService; - _storageService = storageService; + _stateService = stateService; } public string BaseUrl { get; set; } @@ -42,7 +42,11 @@ namespace Bit.Core.Services public async Task SetUrlsFromStorageAsync() { - var urls = await _storageService.GetAsync(Constants.EnvironmentUrlsKey); + var urls = await _stateService.GetEnvironmentUrlsAsync(); + if (urls == null) + { + urls = await _stateService.GetPreAuthEnvironmentUrlsAsync(); + } if (urls == null) { urls = new EnvironmentUrlData(); @@ -72,7 +76,7 @@ namespace Bit.Core.Services urls.Icons = FormatUrl(urls.Icons); urls.Notifications = FormatUrl(urls.Notifications); urls.Events = FormatUrl(urls.Events); - await _storageService.SaveAsync(Constants.EnvironmentUrlsKey, urls); + await _stateService.SetPreAuthEnvironmentUrlsAsync(urls); BaseUrl = urls.Base; WebVaultUrl = urls.WebVault; ApiUrl = urls.Api; diff --git a/src/Core/Services/EventService.cs b/src/Core/Services/EventService.cs index 3a599d17a..179c689c2 100644 --- a/src/Core/Services/EventService.cs +++ b/src/Core/Services/EventService.cs @@ -12,31 +12,31 @@ namespace Bit.Core.Services { public class EventService : IEventService { - private readonly IStorageService _storageService; private readonly IApiService _apiService; - private readonly IUserService _userService; + private readonly IStateService _stateService; + private readonly IOrganizationService _organizationService; private readonly ICipherService _cipherService; public EventService( - IStorageService storageService, IApiService apiService, - IUserService userService, + IStateService stateService, + IOrganizationService organizationService, ICipherService cipherService) { - _storageService = storageService; _apiService = apiService; - _userService = userService; + _stateService = stateService; + _organizationService = organizationService; _cipherService = cipherService; } public async Task CollectAsync(EventType eventType, string cipherId = null, bool uploadImmediately = false) { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; } - var organizations = await _userService.GetAllOrganizationAsync(); + var organizations = await _organizationService.GetAllAsync(); if (organizations == null) { return; @@ -54,7 +54,7 @@ namespace Bit.Core.Services return; } } - var eventCollection = await _storageService.GetAsync>(Constants.EventCollectionKey); + var eventCollection = await _stateService.GetEventCollectionAsync(); if (eventCollection == null) { eventCollection = new List(); @@ -65,7 +65,7 @@ namespace Bit.Core.Services CipherId = cipherId, Date = DateTime.UtcNow }); - await _storageService.SaveAsync(Constants.EventCollectionKey, eventCollection); + await _stateService.SetEventCollectionAsync(eventCollection); if (uploadImmediately) { await UploadEventsAsync(); @@ -74,12 +74,12 @@ namespace Bit.Core.Services public async Task UploadEventsAsync() { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(); if (!authed) { return; } - var eventCollection = await _storageService.GetAsync>(Constants.EventCollectionKey); + var eventCollection = await _stateService.GetEventCollectionAsync(); if (eventCollection == null || !eventCollection.Any()) { return; @@ -100,7 +100,7 @@ namespace Bit.Core.Services public async Task ClearEventsAsync() { - await _storageService.RemoveAsync(Constants.EventCollectionKey); + await _stateService.SetEventCollectionAsync(null); } } } diff --git a/src/Core/Services/FolderService.cs b/src/Core/Services/FolderService.cs index 6eb38ef23..fdc882476 100644 --- a/src/Core/Services/FolderService.cs +++ b/src/Core/Services/FolderService.cs @@ -15,30 +15,25 @@ namespace Bit.Core.Services { public class FolderService : IFolderService { - private const string Keys_CiphersFormat = "ciphers_{0}"; - private const string Keys_FoldersFormat = "folders_{0}"; private const char NestingDelimiter = '/'; private List _decryptedFolderCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IApiService _apiService; - private readonly IStorageService _storageService; private readonly II18nService _i18nService; private readonly ICipherService _cipherService; public FolderService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, IApiService apiService, - IStorageService storageService, II18nService i18nService, ICipherService cipherService) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _apiService = apiService; - _storageService = storageService; _i18nService = i18nService; _cipherService = cipherService; } @@ -60,9 +55,7 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var folders = await _storageService.GetAsync>( - string.Format(Keys_FoldersFormat, userId)); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (!folders?.ContainsKey(id) ?? true) { return null; @@ -72,9 +65,7 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var folders = await _storageService.GetAsync>( - string.Format(Keys_FoldersFormat, userId)); + var folders = await _stateService.GetEncryptedFoldersAsync(); var response = folders?.Select(f => new Folder(f.Value)); return response?.ToList() ?? new List(); } @@ -153,16 +144,14 @@ namespace Bit.Core.Services { response = await _apiService.PutFolderAsync(folder.Id, request); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); var data = new FolderData(response, userId); await UpsertAsync(data); } public async Task UpsertAsync(FolderData folder) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_FoldersFormat, userId); - var folders = await _storageService.GetAsync>(storageKey); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (folders == null) { folders = new Dictionary(); @@ -172,15 +161,13 @@ namespace Bit.Core.Services folders.Add(folder.Id, null); } folders[folder.Id] = folder; - await _storageService.SaveAsync(storageKey, folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; } public async Task UpsertAsync(List folder) { - var userId = await _userService.GetUserIdAsync(); - var storageKey = string.Format(Keys_FoldersFormat, userId); - var folders = await _storageService.GetAsync>(storageKey); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (folders == null) { folders = new Dictionary(); @@ -193,39 +180,35 @@ namespace Bit.Core.Services } folders[f.Id] = f; } - await _storageService.SaveAsync(storageKey, folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; } public async Task ReplaceAsync(Dictionary folders) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_FoldersFormat, userId), folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; } public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_FoldersFormat, userId)); + await _stateService.SetEncryptedFoldersAsync(null, userId); _decryptedFolderCache = null; } public async Task DeleteAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var folderKey = string.Format(Keys_FoldersFormat, userId); - var folders = await _storageService.GetAsync>(folderKey); + var folders = await _stateService.GetEncryptedFoldersAsync(); if (folders == null || !folders.ContainsKey(id)) { return; } folders.Remove(id); - await _storageService.SaveAsync(folderKey, folders); + await _stateService.SetEncryptedFoldersAsync(folders); _decryptedFolderCache = null; // Items in a deleted folder are re-assigned to "No Folder" - var ciphers = await _storageService.GetAsync>( - string.Format(Keys_CiphersFormat, userId)); + var ciphers = await _stateService.GetEncryptedCiphersAsync(); if (ciphers != null) { var updates = new List(); diff --git a/src/Core/Services/KeyConnectorService.cs b/src/Core/Services/KeyConnectorService.cs index 733d79e78..ed8ae5acc 100644 --- a/src/Core/Services/KeyConnectorService.cs +++ b/src/Core/Services/KeyConnectorService.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Bit.Core.Abstractions; -using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; @@ -9,24 +8,20 @@ namespace Bit.Core.Services { public class KeyConnectorService : IKeyConnectorService { - private const string Keys_UsesKeyConnector = "usesKeyConnector"; - - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly ICryptoService _cryptoService; - private readonly IStorageService _storageService; private readonly ITokenService _tokenService; private readonly IApiService _apiService; + private readonly IOrganizationService _organizationService; - private bool? _usesKeyConnector; - - public KeyConnectorService(IUserService userService, ICryptoService cryptoService, - IStorageService storageService, ITokenService tokenService, IApiService apiService) + public KeyConnectorService(IStateService stateService, ICryptoService cryptoService, + ITokenService tokenService, IApiService apiService, OrganizationService organizationService) { - _userService = userService; + _stateService = stateService; _cryptoService = cryptoService; - _storageService = storageService; _tokenService = tokenService; _apiService = apiService; + _organizationService = organizationService; } public async Task GetAndSetKey(string url) @@ -46,23 +41,17 @@ namespace Bit.Core.Services public async Task SetUsesKeyConnector(bool usesKeyConnector) { - _usesKeyConnector = usesKeyConnector; - await _storageService.SaveAsync(Keys_UsesKeyConnector, usesKeyConnector); + await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector); } public async Task GetUsesKeyConnector() { - if (!_usesKeyConnector.HasValue) - { - _usesKeyConnector = await _storageService.GetAsync(Keys_UsesKeyConnector); - } - - return _usesKeyConnector.Value; + return await _stateService.GetUsesKeyConnectorAsync(); } public async Task GetManagingOrganization() { - var orgs = await _userService.GetAllOrganizationAsync(); + var orgs = await _organizationService.GetAllAsync(); return orgs.Find(o => o.UsesKeyConnector && !o.IsAdmin); @@ -88,7 +77,7 @@ namespace Bit.Core.Services public async Task UserNeedsMigration() { - var loggedInUsingSso = _tokenService.GetIsExternal(); + var loggedInUsingSso = await _tokenService.GetIsExternal(); var requiredByOrganization = await GetManagingOrganization() != null; var userIsNotUsingKeyConnector = !await GetUsesKeyConnector(); diff --git a/src/Core/Services/OrganizationService.cs b/src/Core/Services/OrganizationService.cs new file mode 100644 index 000000000..131a9d03d --- /dev/null +++ b/src/Core/Services/OrganizationService.cs @@ -0,0 +1,55 @@ +using Bit.Core.Abstractions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public class OrganizationService : IOrganizationService + { + private readonly IStateService _stateService; + + public OrganizationService(IStateService stateService) + { + _stateService = stateService; + } + + public async Task GetAsync(string id) + { + var organizations = await _stateService.GetOrganizationsAsync(); + if (organizations == null || !organizations.ContainsKey(id)) + { + return null; + } + return new Organization(organizations[id]); + } + + public async Task GetByIdentifierAsync(string identifier) + { + var organizations = await GetAllAsync(); + if (organizations == null || organizations.Count == 0) + { + return null; + } + return organizations.FirstOrDefault(o => o.Identifier == identifier); + } + + public async Task> GetAllAsync(string userId = null) + { + var organizations = await _stateService.GetOrganizationsAsync(userId); + return organizations?.Select(o => new Organization(o.Value)).ToList() ?? new List(); + } + + public async Task ReplaceAsync(Dictionary organizations) + { + await _stateService.SetOrganizationsAsync(organizations); + } + + public async Task ClearAllAsync(string userId) + { + await _stateService.SetOrganizationsAsync(null, userId); + } + } +} diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index 746d9b345..cc776052d 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -14,8 +14,6 @@ namespace Bit.Core.Services { public class PasswordGenerationService : IPasswordGenerationService { - private const string Keys_Options = "passwordGenerationOptions"; - private const string Keys_History = "generatedPasswordHistory"; private const int MaxPasswordsInHistory = 100; private const string LowercaseCharSet = "abcdefghijkmnopqrstuvwxyz"; private const string UppercaseCharSet = "ABCDEFGHJKLMNPQRSTUVWXYZ"; @@ -23,7 +21,7 @@ namespace Bit.Core.Services private const string SpecialCharSet = "!@#$%^&*"; private readonly ICryptoService _cryptoService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; private readonly IPolicyService _policyService; private PasswordGenerationOptions _defaultOptions = new PasswordGenerationOptions(true); @@ -32,12 +30,12 @@ namespace Bit.Core.Services public PasswordGenerationService( ICryptoService cryptoService, - IStorageService storageService, + IStateService stateService, ICryptoFunctionService cryptoFunctionService, IPolicyService policyService) { _cryptoService = cryptoService; - _storageService = storageService; + _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; _policyService = policyService; } @@ -160,6 +158,12 @@ namespace Bit.Core.Services return password.ToString(); } + public void ClearCache() + { + _optionsCache = null; + _history = null; + } + public async Task GeneratePassphraseAsync(PasswordGenerationOptions options) { options.Merge(_defaultOptions); @@ -204,7 +208,7 @@ namespace Bit.Core.Services { if (_optionsCache == null) { - var options = await _storageService.GetAsync(Keys_Options); + var options = await _stateService.GetPasswordGenerationOptionsAsync(); if (options == null) { _optionsCache = _defaultOptions; @@ -432,7 +436,7 @@ namespace Bit.Core.Services public async Task SaveOptionsAsync(PasswordGenerationOptions options) { - await _storageService.SaveAsync(Keys_Options, options); + await _stateService.SetPasswordGenerationOptionsAsync(options); _optionsCache = options; } @@ -445,7 +449,7 @@ namespace Bit.Core.Services } if (_history == null) { - var encrypted = await _storageService.GetAsync>(Keys_History); + var encrypted = await _stateService.GetEncryptedPasswordGenerationHistory(); _history = await DecryptHistoryAsync(encrypted); } return _history ?? new List(); @@ -473,13 +477,13 @@ namespace Bit.Core.Services } var newHistory = await EncryptHistoryAsync(currentHistory); token.ThrowIfCancellationRequested(); - await _storageService.SaveAsync(Keys_History, newHistory); + await _stateService.SetEncryptedPasswordGenerationHistoryAsync(newHistory); } - public async Task ClearAsync() + public async Task ClearAsync(string userId = null) { _history = new List(); - await _storageService.RemoveAsync(Keys_History); + await _stateService.SetEncryptedPasswordGenerationHistoryAsync(null, userId); } public Result PasswordStrength(string password, List userInputs = null) diff --git a/src/Core/Services/PolicyService.cs b/src/Core/Services/PolicyService.cs index 95529ab26..e1c2b8c75 100644 --- a/src/Core/Services/PolicyService.cs +++ b/src/Core/Services/PolicyService.cs @@ -12,19 +12,17 @@ namespace Bit.Core.Services { public class PolicyService : IPolicyService { - private const string Keys_PoliciesPrefix = "policies_{0}"; - - private readonly IStorageService _storageService; - private readonly IUserService _userService; + private readonly IStateService _stateService; + private readonly IOrganizationService _organizationService; private IEnumerable _policyCache; public PolicyService( - IStorageService storageService, - IUserService userService) + IStateService stateService, + IOrganizationService organizationService) { - _storageService = storageService; - _userService = userService; + _stateService = stateService; + _organizationService = organizationService; } public void ClearCache() @@ -32,13 +30,11 @@ namespace Bit.Core.Services _policyCache = null; } - public async Task> GetAll(PolicyType? type) + public async Task> GetAll(PolicyType? type, string userId = null) { if (_policyCache == null) { - var userId = await _userService.GetUserIdAsync(); - var policies = await _storageService.GetAsync>( - string.Format(Keys_PoliciesPrefix, userId)); + var policies = await _stateService.GetEncryptedPoliciesAsync(userId); if (policies == null) { return null; @@ -56,27 +52,26 @@ namespace Bit.Core.Services } } - public async Task Replace(Dictionary policies) + public async Task Replace(Dictionary policies, string userId = null) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_PoliciesPrefix, userId), policies); + await _stateService.SetEncryptedPoliciesAsync(policies, userId); _policyCache = null; } - public async Task Clear(string userId) + public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_PoliciesPrefix, userId)); + await _stateService.SetEncryptedPoliciesAsync(null, userId); _policyCache = null; } public async Task GetMasterPasswordPolicyOptions( - IEnumerable policies = null) + IEnumerable policies = null, string userId = null) { MasterPasswordPolicyOptions enforcedOptions = null; if (policies == null) { - policies = await GetAll(PolicyType.MasterPassword); + policies = await GetAll(PolicyType.MasterPassword, userId); } else { @@ -198,14 +193,14 @@ namespace Bit.Core.Services return new Tuple(resetPasswordPolicyOptions, policy != null); } - public async Task PolicyAppliesToUser(PolicyType policyType, Func policyFilter) + public async Task PolicyAppliesToUser(PolicyType policyType, Func policyFilter, string userId = null) { - var policies = await GetAll(policyType); + var policies = await GetAll(policyType, userId); if (policies == null) { return false; } - var organizations = await _userService.GetAllOrganizationAsync(); + var organizations = await _organizationService.GetAllAsync(userId); IEnumerable filteredPolicies; diff --git a/src/Core/Services/SendService.cs b/src/Core/Services/SendService.cs index d0b535df8..1ca9d4930 100644 --- a/src/Core/Services/SendService.cs +++ b/src/Core/Services/SendService.cs @@ -20,9 +20,8 @@ namespace Bit.Core.Services { private List _decryptedSendsCache; private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IApiService _apiService; - private readonly IStorageService _storageService; private readonly II18nService _i18nService; private readonly ICryptoFunctionService _cryptoFunctionService; private Task> _getAllDecryptedTask; @@ -30,27 +29,23 @@ namespace Bit.Core.Services public SendService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, IApiService apiService, IFileUploadService fileUploadService, - IStorageService storageService, II18nService i18nService, ICryptoFunctionService cryptoFunctionService) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _apiService = apiService; _fileUploadService = fileUploadService; - _storageService = storageService; _i18nService = i18nService; _cryptoFunctionService = cryptoFunctionService; } - public static string GetSendKey(string userId) => string.Format("sends_{0}", userId); - public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(GetSendKey(userId)); + await _stateService.SetEncryptedSendsAsync(null, userId); ClearCache(); } @@ -58,8 +53,7 @@ namespace Bit.Core.Services public async Task DeleteAsync(params string[] ids) { - var userId = await _userService.GetUserIdAsync(); - var sends = await _storageService.GetAsync>(GetSendKey(userId)); + var sends = await _stateService.GetEncryptedSendsAsync(); if (sends == null) { @@ -71,7 +65,7 @@ namespace Bit.Core.Services sends.Remove(id); } - await _storageService.SaveAsync(GetSendKey(userId), sends); + await _stateService.SetEncryptedSendsAsync(sends); ClearCache(); } @@ -138,8 +132,7 @@ namespace Bit.Core.Services public async Task> GetAllAsync() { - var userId = await _userService.GetUserIdAsync(); - var sends = await _storageService.GetAsync>(GetSendKey(userId)); + var sends = await _stateService.GetEncryptedSendsAsync(); return sends?.Select(kvp => new Send(kvp.Value)).ToList() ?? new List(); } @@ -179,8 +172,7 @@ namespace Bit.Core.Services public async Task GetAsync(string id) { - var userId = await _userService.GetUserIdAsync(); - var sends = await _storageService.GetAsync>(GetSendKey(userId)); + var sends = await _stateService.GetEncryptedSendsAsync(); if (sends == null || !sends.ContainsKey(id)) { @@ -192,8 +184,7 @@ namespace Bit.Core.Services public async Task ReplaceAsync(Dictionary sends) { - var userId = await _userService.GetUserIdAsync(); - await _storageService.SaveAsync(GetSendKey(userId), sends); + await _stateService.SetEncryptedSendsAsync(sends); _decryptedSendsCache = null; } @@ -237,7 +228,7 @@ namespace Bit.Core.Services response = await _apiService.PutSendAsync(send.Id, request); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await UpsertAsync(new SendData(response, userId)); return response.Id; } @@ -255,8 +246,7 @@ namespace Bit.Core.Services public async Task UpsertAsync(params SendData[] sends) { - var userId = await _userService.GetUserIdAsync(); - var knownSends = await _storageService.GetAsync>(GetSendKey(userId)) ?? + var knownSends = await _stateService.GetEncryptedSendsAsync() ?? new Dictionary(); foreach (var send in sends) @@ -264,14 +254,14 @@ namespace Bit.Core.Services knownSends[send.Id] = send; } - await _storageService.SaveAsync(GetSendKey(userId), knownSends); + await _stateService.SetEncryptedSendsAsync(knownSends); _decryptedSendsCache = null; } public async Task RemovePasswordWithServerAsync(string id) { var response = await _apiService.PutSendRemovePasswordAsync(id); - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await UpsertAsync(new SendData(response, userId)); } diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs index c0e776009..14984bc72 100644 --- a/src/Core/Services/SettingsService.cs +++ b/src/Core/Services/SettingsService.cs @@ -1,7 +1,5 @@ using Bit.Core.Abstractions; -using Bit.Core.Utilities; using Newtonsoft.Json.Linq; -using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -9,20 +7,16 @@ namespace Bit.Core.Services { public class SettingsService : ISettingsService { - private const string Keys_SettingsFormat = "settings_{0}"; private const string Keys_EquivalentDomains = "equivalentDomains"; - private readonly IUserService _userService; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private Dictionary _settingsCache; public SettingsService( - IUserService userService, - IStorageService storageService) + IStateService stateService) { - _userService = userService; - _storageService = storageService; + _stateService = stateService; } public void ClearCache() @@ -49,7 +43,7 @@ namespace Bit.Core.Services public async Task ClearAsync(string userId) { - await _storageService.RemoveAsync(string.Format(Keys_SettingsFormat, userId)); + await _stateService.SetSettingsAsync(null, userId); ClearCache(); } @@ -59,16 +53,13 @@ namespace Bit.Core.Services { if (_settingsCache == null) { - var userId = await _userService.GetUserIdAsync(); - _settingsCache = await _storageService.GetAsync>( - string.Format(Keys_SettingsFormat, userId)); + _settingsCache = await _stateService.GetSettingsAsync(); } return _settingsCache; } private async Task SetSettingsKeyAsync(string key, T value) { - var userId = await _userService.GetUserIdAsync(); var settings = await GetSettingsAsync(); if (settings == null) { @@ -82,7 +73,7 @@ namespace Bit.Core.Services { settings.Add(key, value); } - await _storageService.SaveAsync(string.Format(Keys_SettingsFormat, userId), settings); + await _stateService.SetSettingsAsync(settings); _settingsCache = settings; } } diff --git a/src/Core/Services/StateMigrationService.cs b/src/Core/Services/StateMigrationService.cs new file mode 100644 index 000000000..ce3c7cf92 --- /dev/null +++ b/src/Core/Services/StateMigrationService.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; +using Bit.Core.Utilities; +using Newtonsoft.Json; + +namespace Bit.Core.Services +{ + public class StateMigrationService : IStateMigrationService + { + private const int StateVersion = 3; + + private readonly IStorageService _preferencesStorageService; + private readonly IStorageService _liteDbStorageService; + private readonly IStorageService _secureStorageService; + + private enum Storage + { + LiteDb, + Prefs, + Secure, + } + + public StateMigrationService(IStorageService liteDbStorageService, IStorageService preferenceStorageService, + IStorageService secureStorageService) + { + _liteDbStorageService = liteDbStorageService; + _preferencesStorageService = preferenceStorageService; + _secureStorageService = secureStorageService; + } + + public async Task NeedsMigration() + { + var lastVersion = await GetLastStateVersionAsync(); + if (lastVersion == 0) + { + // fresh install, set current/latest version for availability going forward + lastVersion = StateVersion; + await SetLastStateVersionAsync(lastVersion); + } + return lastVersion < StateVersion; + } + + public async Task Migrate() + { + var lastVersion = await GetLastStateVersionAsync(); + switch (lastVersion) + { + case 1: + await MigrateFrom1To2Async(); + goto case 2; + case 2: + await MigrateFrom2To3Async(); + break; + } + } + + // v1 to v2 Migration + + private class V1Keys + { + internal const string EnvironmentUrlsKey = "environmentUrls"; + + } + + private async Task MigrateFrom1To2Async() + { + // move environmentUrls from LiteDB to prefs + var environmentUrls = await GetValueAsync(Storage.LiteDb, V1Keys.EnvironmentUrlsKey); + if (environmentUrls == null) + { + throw new Exception("'environmentUrls' must be in LiteDB during migration from 1 to 2"); + } + await SetValueAsync(Storage.Prefs, V2Keys.EnvironmentUrlsKey, environmentUrls); + + // Update stored version + await SetLastStateVersionAsync(2); + + // Remove old data + await RemoveValueAsync(Storage.LiteDb, V1Keys.EnvironmentUrlsKey); + } + + // v2 to v3 Migration + + private class V2Keys + { + internal const string SyncOnRefreshKey = "syncOnRefresh"; + internal const string VaultTimeoutKey = "lockOption"; + internal const string VaultTimeoutActionKey = "vaultTimeoutAction"; + internal const string LastActiveTimeKey = "lastActiveTime"; + internal const string BiometricUnlockKey = "fingerprintUnlock"; + internal const string ProtectedPin = "protectedPin"; + internal const string PinProtectedKey = "pinProtectedKey"; + internal const string DefaultUriMatch = "defaultUriMatch"; + internal const string DisableAutoTotpCopyKey = "disableAutoTotpCopy"; + internal const string EnvironmentUrlsKey = "environmentUrls"; + internal const string AutofillDisableSavePromptKey = "autofillDisableSavePrompt"; + internal const string AutofillBlacklistedUrisKey = "autofillBlacklistedUris"; + internal const string DisableFaviconKey = "disableFavicon"; + internal const string ThemeKey = "theme"; + internal const string ClearClipboardKey = "clearClipboard"; + internal const string PreviousPageKey = "previousPage"; + internal const string InlineAutofillEnabledKey = "inlineAutofillEnabled"; + internal const string InvalidUnlockAttempts = "invalidUnlockAttempts"; + internal const string PasswordRepromptAutofillKey = "passwordRepromptAutofillKey"; + internal const string PasswordVerifiedAutofillKey = "passwordVerifiedAutofillKey"; + internal const string MigratedFromV1 = "migratedFromV1"; + internal const string MigratedFromV1AutofillPromptShown = "migratedV1AutofillPromptShown"; + internal const string TriedV1Resync = "triedV1Resync"; + internal const string Keys_UserId = "userId"; + internal const string Keys_UserEmail = "userEmail"; + internal const string Keys_Stamp = "securityStamp"; + internal const string Keys_Kdf = "kdf"; + internal const string Keys_KdfIterations = "kdfIterations"; + internal const string Keys_EmailVerified = "emailVerified"; + internal const string Keys_ForcePasswordReset = "forcePasswordReset"; + internal const string Keys_AccessToken = "accessToken"; + internal const string Keys_RefreshToken = "refreshToken"; + internal const string Keys_LocalData = "ciphersLocalData"; + internal const string Keys_NeverDomains = "neverDomains"; + internal const string Keys_Key = "key"; + internal const string Keys_EncOrgKeys = "encOrgKeys"; + internal const string Keys_EncPrivateKey = "encPrivateKey"; + internal const string Keys_EncKey = "encKey"; + internal const string Keys_KeyHash = "keyHash"; + internal const string Keys_UsesKeyConnector = "usesKeyConnector"; + internal const string Keys_PassGenOptions = "passwordGenerationOptions"; + internal const string Keys_PassGenHistory = "generatedPasswordHistory"; + } + + private async Task MigrateFrom2To3Async() + { + // build account and state + var userId = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_UserId); + var email = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_UserEmail); + string name = null; + var hasPremiumPersonally = false; + var accessToken = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_AccessToken); + if (!string.IsNullOrWhiteSpace(accessToken)) + { + var tokenService = ServiceContainer.Resolve("tokenService"); + await tokenService.SetAccessTokenAsync(accessToken, true); + + if (string.IsNullOrWhiteSpace(userId)) + { + userId = tokenService.GetUserId(); + } + if (string.IsNullOrWhiteSpace(email)) + { + email = tokenService.GetEmail(); + } + name = tokenService.GetName(); + hasPremiumPersonally = tokenService.GetPremium(); + } + if (string.IsNullOrWhiteSpace(userId)) + { + throw new Exception("'userId' must be in LiteDB during migration from 2 to 3"); + } + + var kdfType = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_Kdf); + var kdfIterations = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_KdfIterations); + var stamp = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_Stamp); + var emailVerified = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_EmailVerified); + var refreshToken = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_RefreshToken); + var account = new Account( + new Account.AccountProfile() + { + UserId = userId, + Email = email, + Name = name, + Stamp = stamp, + KdfType = (KdfType?)kdfType, + KdfIterations = kdfIterations, + EmailVerified = emailVerified, + HasPremiumPersonally = hasPremiumPersonally, + }, + new Account.AccountTokens() + { + AccessToken = accessToken, + RefreshToken = refreshToken, + } + ); + var environmentUrls = await GetValueAsync(Storage.Prefs, V2Keys.EnvironmentUrlsKey); + var vaultTimeout = await GetValueAsync(Storage.Prefs, V2Keys.VaultTimeoutKey); + var vaultTimeoutAction = await GetValueAsync(Storage.Prefs, V2Keys.VaultTimeoutActionKey); + account.Settings = new Account.AccountSettings() + { + EnvironmentUrls = environmentUrls, + VaultTimeout = vaultTimeout, + VaultTimeoutAction = + vaultTimeoutAction == "logout" ? VaultTimeoutAction.Logout : VaultTimeoutAction.Lock, + }; + var state = new State { Accounts = new Dictionary { [userId] = account } }; + state.ActiveUserId = userId; + await SetValueAsync(Storage.LiteDb, Constants.StateKey, state); + + // migrate user-specific non-state data + var syncOnRefresh = await GetValueAsync(Storage.LiteDb, V2Keys.SyncOnRefreshKey); + await SetValueAsync(Storage.LiteDb, Constants.SyncOnRefreshKey(userId), syncOnRefresh); + var lastActiveTime = await GetValueAsync(Storage.Prefs, V2Keys.LastActiveTimeKey); + await SetValueAsync(Storage.LiteDb, Constants.LastActiveTimeKey(userId), lastActiveTime); + var biometricUnlock = await GetValueAsync(Storage.LiteDb, V2Keys.BiometricUnlockKey); + await SetValueAsync(Storage.LiteDb, Constants.BiometricUnlockKey(userId), biometricUnlock); + var protectedPin = await GetValueAsync(Storage.LiteDb, V2Keys.ProtectedPin); + await SetValueAsync(Storage.LiteDb, Constants.ProtectedPinKey(userId), protectedPin); + var pinProtectedKey = await GetValueAsync(Storage.LiteDb, V2Keys.PinProtectedKey); + await SetValueAsync(Storage.LiteDb, Constants.PinProtectedKey(userId), pinProtectedKey); + var defaultUriMatch = await GetValueAsync(Storage.Prefs, V2Keys.DefaultUriMatch); + await SetValueAsync(Storage.LiteDb, Constants.DefaultUriMatchKey(userId), defaultUriMatch); + var disableAutoTotpCopy = await GetValueAsync(Storage.Prefs, V2Keys.DisableAutoTotpCopyKey); + await SetValueAsync(Storage.LiteDb, Constants.DisableAutoTotpCopyKey(userId), disableAutoTotpCopy); + var autofillDisableSavePrompt = + await GetValueAsync(Storage.Prefs, V2Keys.AutofillDisableSavePromptKey); + await SetValueAsync(Storage.LiteDb, Constants.AutofillDisableSavePromptKey(userId), + autofillDisableSavePrompt); + var autofillBlacklistedUris = + await GetValueAsync>(Storage.LiteDb, V2Keys.AutofillBlacklistedUrisKey); + await SetValueAsync(Storage.LiteDb, Constants.AutofillBlacklistedUrisKey(userId), autofillBlacklistedUris); + var disableFavicon = await GetValueAsync(Storage.Prefs, V2Keys.DisableFaviconKey); + await SetValueAsync(Storage.LiteDb, Constants.DisableFaviconKey(userId), disableFavicon); + var theme = await GetValueAsync(Storage.Prefs, V2Keys.ThemeKey); + await SetValueAsync(Storage.LiteDb, Constants.ThemeKey(userId), theme); + var clearClipboard = await GetValueAsync(Storage.Prefs, V2Keys.ClearClipboardKey); + await SetValueAsync(Storage.LiteDb, Constants.ClearClipboardKey(userId), clearClipboard); + var previousPage = await GetValueAsync(Storage.LiteDb, V2Keys.PreviousPageKey); + await SetValueAsync(Storage.LiteDb, Constants.PreviousPageKey(userId), previousPage); + var inlineAutofillEnabled = await GetValueAsync(Storage.Prefs, V2Keys.InlineAutofillEnabledKey); + await SetValueAsync(Storage.LiteDb, Constants.InlineAutofillEnabledKey(userId), inlineAutofillEnabled); + var invalidUnlockAttempts = await GetValueAsync(Storage.Prefs, V2Keys.InvalidUnlockAttempts); + await SetValueAsync(Storage.LiteDb, Constants.InvalidUnlockAttemptsKey(userId), invalidUnlockAttempts); + var passwordRepromptAutofill = + await GetValueAsync(Storage.LiteDb, V2Keys.PasswordRepromptAutofillKey); + await SetValueAsync(Storage.LiteDb, Constants.PasswordRepromptAutofillKey(userId), + passwordRepromptAutofill); + var passwordVerifiedAutofill = + await GetValueAsync(Storage.LiteDb, V2Keys.PasswordVerifiedAutofillKey); + await SetValueAsync(Storage.LiteDb, Constants.PasswordVerifiedAutofillKey(userId), + passwordVerifiedAutofill); + var cipherLocalData = await GetValueAsync>>(Storage.LiteDb, + V2Keys.Keys_LocalData); + await SetValueAsync(Storage.LiteDb, Constants.LocalDataKey(userId), cipherLocalData); + var neverDomains = await GetValueAsync>(Storage.LiteDb, V2Keys.Keys_NeverDomains); + await SetValueAsync(Storage.LiteDb, Constants.NeverDomainsKey(userId), neverDomains); + var key = await GetValueAsync(Storage.Secure, V2Keys.Keys_Key); + await SetValueAsync(Storage.Secure, Constants.KeyKey(userId), key); + var encOrgKeys = await GetValueAsync>(Storage.LiteDb, V2Keys.Keys_EncOrgKeys); + await SetValueAsync(Storage.LiteDb, Constants.EncOrgKeysKey(userId), encOrgKeys); + var encPrivateKey = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_EncPrivateKey); + await SetValueAsync(Storage.LiteDb, Constants.EncPrivateKeyKey(userId), encPrivateKey); + var encKey = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_EncKey); + await SetValueAsync(Storage.LiteDb, Constants.EncKeyKey(userId), encKey); + var keyHash = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_KeyHash); + await SetValueAsync(Storage.LiteDb, Constants.KeyHashKey(userId), keyHash); + var usesKeyConnector = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_UsesKeyConnector); + await SetValueAsync(Storage.LiteDb, Constants.UsesKeyConnectorKey(userId), usesKeyConnector); + var passGenOptions = + await GetValueAsync(Storage.LiteDb, V2Keys.Keys_PassGenOptions); + await SetValueAsync(Storage.LiteDb, Constants.PassGenOptionsKey(userId), passGenOptions); + var passGenHistory = + await GetValueAsync>(Storage.LiteDb, V2Keys.Keys_PassGenHistory); + await SetValueAsync(Storage.LiteDb, Constants.PassGenHistoryKey(userId), passGenHistory); + + // migrate global non-state data + await SetValueAsync(Storage.Prefs, Constants.PreAuthEnvironmentUrlsKey, environmentUrls); + + // Update stored version + await SetLastStateVersionAsync(3); + + // Remove old data + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_UserId); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_UserEmail); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_AccessToken); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_RefreshToken); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_Kdf); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_KdfIterations); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_Stamp); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_EmailVerified); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_ForcePasswordReset); + await RemoveValueAsync(Storage.Prefs, V2Keys.EnvironmentUrlsKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.PinProtectedKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.VaultTimeoutKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.VaultTimeoutActionKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.SyncOnRefreshKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.LastActiveTimeKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.BiometricUnlockKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.ProtectedPin); + await RemoveValueAsync(Storage.Prefs, V2Keys.DefaultUriMatch); + await RemoveValueAsync(Storage.Prefs, V2Keys.DisableAutoTotpCopyKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.AutofillDisableSavePromptKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.AutofillBlacklistedUrisKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.DisableFaviconKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.ThemeKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.ClearClipboardKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.PreviousPageKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.InlineAutofillEnabledKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.InvalidUnlockAttempts); + await RemoveValueAsync(Storage.LiteDb, V2Keys.PasswordRepromptAutofillKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.PasswordVerifiedAutofillKey); + await RemoveValueAsync(Storage.Prefs, V2Keys.MigratedFromV1); + await RemoveValueAsync(Storage.Prefs, V2Keys.MigratedFromV1AutofillPromptShown); + await RemoveValueAsync(Storage.Prefs, V2Keys.TriedV1Resync); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_LocalData); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_NeverDomains); + await RemoveValueAsync(Storage.Secure, V2Keys.Keys_Key); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_EncOrgKeys); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_EncPrivateKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_EncKey); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_KeyHash); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_UsesKeyConnector); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_PassGenOptions); + await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_PassGenHistory); + } + + // Helpers + + private async Task GetLastStateVersionAsync() + { + var lastVersion = await GetValueAsync(Storage.Prefs, Constants.StateVersionKey); + if (lastVersion != null) + { + return lastVersion.Value; + } + + // check for original v1 migration + var envUrlsLiteDb = await GetValueAsync(Storage.LiteDb, V1Keys.EnvironmentUrlsKey); + if (envUrlsLiteDb != null) + { + // environmentUrls still in LiteDB (never migrated to prefs), this is v1 + return 1; + } + + // check for original v2 migration + var envUrlsPrefs = await GetValueAsync(Storage.Prefs, V2Keys.EnvironmentUrlsKey); + if (envUrlsPrefs != null) + { + // environmentUrls in Prefs (migrated from LiteDB), this is v2 + return 2; + } + + // this is a fresh install + return 0; + } + + private async Task SetLastStateVersionAsync(int value) + { + await SetValueAsync(Storage.Prefs, Constants.StateVersionKey, value); + } + + private async Task GetValueAsync(Storage storage, string key) + { + var value = await GetStorageService(storage).GetAsync(key); + Log("GET", storage, key, JsonConvert.SerializeObject(value)); + return value; + } + + private async Task SetValueAsync(Storage storage, string key, T value) + { + if (value == null) + { + await RemoveValueAsync(storage, key); + return; + } + Log("SET", storage, key, JsonConvert.SerializeObject(value)); + await GetStorageService(storage).SaveAsync(key, value); + } + + private async Task RemoveValueAsync(Storage storage, string key) + { + Log("REMOVE", storage, key, null); + await GetStorageService(storage).RemoveAsync(key); + } + + private IStorageService GetStorageService(Storage storage) + { + switch (storage) + { + case Storage.Secure: + return _secureStorageService; + case Storage.Prefs: + return _preferencesStorageService; + default: + return _liteDbStorageService; + } + } + + private void Log(string tag, Storage storage, string key, string value) + { + // TODO Remove this once all bugs are squished + string text; + switch (storage) + { + case Storage.Secure: + text = "SECURE / "; + break; + case Storage.Prefs: + text = "PREFS / "; + break; + default: + text = "LITEDB / "; + break; + } + text += "Key: " + key + " / "; + if (value != null) + { + text += "Value: " + value; + } + Debug.WriteLine(text, ">>> " + tag); + } + } +} diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index a40bd6fb7..ba06c70ac 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1,44 +1,1546 @@ -using Bit.Core.Abstractions; +using System; +using Bit.Core.Abstractions; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; +using Bit.Core.Models.View; +using Bit.Core.Utilities; +using Newtonsoft.Json; namespace Bit.Core.Services { public class StateService : IStateService { - private readonly Dictionary _state = new Dictionary(); + private readonly IStorageService _storageService; + private readonly IStorageService _secureStorageService; - public Task GetAsync(string key) + private State _state; + private bool _migrationChecked; + + public bool BiometricLocked { get; set; } + + public List AccountViews { get; set; } + + public StateService(IStorageService storageService, IStorageService secureStorageService) { - return Task.FromResult(_state.ContainsKey(key) ? (T)_state[key] : (T)(object)null); + _storageService = storageService; + _secureStorageService = secureStorageService; } - public Task SaveAsync(string key, T obj) + public async Task GetActiveUserIdAsync() { - if (_state.ContainsKey(key)) + await CheckStateAsync(); + + var activeUserId = _state?.ActiveUserId; + if (activeUserId == null) { - _state[key] = obj; + var state = await GetStateFromStorageAsync(); + activeUserId = state?.ActiveUserId; + } + return activeUserId; + } + + public async Task SetActiveUserAsync(string userId) + { + if (userId != null) + { + await ValidateUserAsync(userId); + } + await CheckStateAsync(); + var state = await GetStateFromStorageAsync(); + state.ActiveUserId = userId; + await SaveStateToStorageAsync(state); + _state.ActiveUserId = userId; + + // Update pre-auth settings based on now-active user + await SetRememberedEmailAsync(await GetEmailAsync()); + await SetRememberedOrgIdentifierAsync(await GetRememberedOrgIdentifierAsync()); + await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync()); + } + + public async Task IsAuthenticatedAsync(string userId = null) + { + return await GetAccessTokenAsync(userId) != null; + } + + public async Task GetUserIdAsync(string email) + { + if (string.IsNullOrWhiteSpace(email)) + { + throw new ArgumentNullException(nameof(email)); + } + + await CheckStateAsync(); + if (_state?.Accounts != null) + { + foreach (var account in _state.Accounts) + { + var accountEmail = account.Value?.Profile?.Email; + if (accountEmail == email) + { + return account.Value.Profile.UserId; + } + } + } + return null; + } + + public async Task RefreshAccountViewsAsync(bool allowAddAccountRow) + { + await CheckStateAsync(); + + if (AccountViews == null) + { + AccountViews = new List(); } else { - _state.Add(key, obj); + AccountViews.Clear(); } - return Task.FromResult(0); - } - public Task RemoveAsync(string key) - { - if (_state.ContainsKey(key)) + var accountList = _state?.Accounts?.Values.ToList(); + if (accountList == null) { - _state.Remove(key); + return; + } + var vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); + foreach (var account in accountList) + { + var isActiveAccount = account.Profile.UserId == _state.ActiveUserId; + var accountView = new AccountView(account, isActiveAccount); + if (isActiveAccount) + { + AccountViews.Add(accountView); + continue; + } + var isLocked = await vaultTimeoutService.IsLockedAsync(accountView.UserId); + var shouldTimeout = await vaultTimeoutService.ShouldTimeoutAsync(accountView.UserId); + if (isLocked || shouldTimeout) + { + var action = account.Settings.VaultTimeoutAction; + accountView.AuthStatus = action == VaultTimeoutAction.Logout ? AuthenticationStatus.LoggedOut + : AuthenticationStatus.Locked; + } + else + { + accountView.AuthStatus = AuthenticationStatus.Unlocked; + } + AccountViews.Add(accountView); + } + if (allowAddAccountRow && AccountViews.Count < Constants.MaxAccounts) + { + AccountViews.Add(new AccountView()); } - return Task.FromResult(0); } - public Task PurgeAsync() + public async Task AddAccountAsync(Account account) { - _state.Clear(); - return Task.FromResult(0); + await ScaffoldNewAccountAsync(account); + await SetActiveUserAsync(account.Profile.UserId); + await RefreshAccountViewsAsync(true); + } + + public async Task LogoutAccountAsync(string userId, bool userInitiated) + { + if (string.IsNullOrWhiteSpace(userId)) + { + throw new ArgumentNullException(nameof(userId)); + } + + await CheckStateAsync(); + await RemoveAccountAsync(userId, userInitiated); + + // If user initiated logout (not vault timeout) find the next user to make active, if any + if (userInitiated && _state?.Accounts != null) + { + foreach (var account in _state.Accounts) + { + var uid = account.Value?.Profile?.UserId; + if (uid == null) + { + continue; + } + await SetActiveUserAsync(uid); + break; + } + } + } + + public async Task GetPreAuthEnvironmentUrlsAsync() + { + return await GetValueAsync( + Constants.PreAuthEnvironmentUrlsKey, await GetDefaultStorageOptionsAsync()); + } + + public async Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value) + { + await SetValueAsync( + Constants.PreAuthEnvironmentUrlsKey, value, await GetDefaultStorageOptionsAsync()); + } + + public async Task GetEnvironmentUrlsAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Settings?.EnvironmentUrls; + } + + public async Task GetBiometricUnlockAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetBiometricUnlockAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task CanAccessPremiumAsync(string userId = null) + { + if (userId == null) + { + userId = await GetActiveUserIdAsync(); + } + if (!await IsAuthenticatedAsync(userId)) + { + return false; + } + + var account = await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())); + if (account?.Profile?.HasPremiumPersonally.GetValueOrDefault() ?? false) + { + return true; + } + + var organizationService = ServiceContainer.Resolve("organizationService"); + var organizations = await organizationService.GetAllAsync(userId); + return organizations?.Any(o => o.UsersGetPremium && o.Enabled) ?? false; + } + + public async Task GetProtectedPinAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ProtectedPinKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetProtectedPinAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ProtectedPinKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPinProtectedAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PinProtectedKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPinProtectedAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PinProtectedKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPinProtectedKeyAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) + ))?.Keys?.PinProtectedKey; + } + + public async Task SetPinProtectedKeyAsync(EncString value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.PinProtectedKey = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKdfTypeAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.KdfType; + } + + public async Task SetKdfTypeAsync(KdfType? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.KdfType = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKdfIterationsAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.KdfIterations; + } + + public async Task SetKdfIterationsAsync(int? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.KdfIterations = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKeyEncryptedAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultSecureStorageOptionsAsync()); + var key = Constants.KeyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetKeyEncryptedAsync(string value, string userId) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultSecureStorageOptionsAsync()); + var key = Constants.KeyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetKeyDecryptedAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) + ))?.Keys?.Key; + } + + public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Keys.Key = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetKeyHashAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.KeyHashKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetKeyHashAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.KeyHashKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetEncKeyEncryptedAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.EncKeyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetEncKeyEncryptedAsync(string value, string userId) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.EncKeyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetOrgKeysEncryptedAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.EncOrgKeysKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetOrgKeysEncryptedAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.EncOrgKeysKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPrivateKeyEncryptedAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.EncPrivateKeyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPrivateKeyEncryptedAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.EncPrivateKeyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetAutofillBlacklistedUrisAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetAutofillBlacklistedUrisAsync(List value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillBlacklistedUrisKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAutofillTileAddedAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.AutofillTileAdded; + return await GetValueAsync(key, options); + } + + public async Task SetAutofillTileAddedAsync(bool? value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.AutofillTileAdded; + await SetValueAsync(key, value, options); + } + + public async Task GetEmailAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.Email; + } + + public async Task GetNameAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.Name; + } + + public async Task GetOrgIdentifierAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.OrgIdentifier; + } + + public async Task GetLastActiveTimeAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LastActiveTimeKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastActiveTimeAsync(long? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LastActiveTimeKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetVaultTimeoutAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Settings?.VaultTimeout; + } + + public async Task SetVaultTimeoutAsync(int? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Settings.VaultTimeout = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetVaultTimeoutActionAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Settings?.VaultTimeoutAction; + } + + public async Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Settings.VaultTimeoutAction = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetLastFileCacheClearAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LastFileCacheClearKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastFileCacheClearAsync(DateTime? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LastFileCacheClearKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPreviousPageInfoAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PreviousPageKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PreviousPageKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetInvalidUnlockAttemptsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetInvalidUnlockAttemptsAsync(int? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.InvalidUnlockAttemptsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetLastBuildAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.LastBuildKey; + return await GetValueAsync(key, options); + } + + public async Task SetLastBuildAsync(string value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.LastBuildKey; + await SetValueAsync(key, value, options); + } + + public async Task GetDisableFaviconAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableFaviconKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetDisableFaviconAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableFaviconKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetDisableAutoTotpCopyAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetDisableAutoTotpCopyAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.DisableAutoTotpCopyKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetInlineAutofillEnabledAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.InlineAutofillEnabledKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetInlineAutofillEnabledAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.InlineAutofillEnabledKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAutofillDisableSavePromptAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAutofillDisableSavePromptAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.AutofillDisableSavePromptKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task>> GetLocalDataAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LocalDataKey(reconciledOptions.UserId); + return await GetValueAsync>>(key, reconciledOptions); + } + + public async Task SetLocalDataAsync(Dictionary> value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LocalDataKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedCiphersAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.CiphersKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedCiphersAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.CiphersKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetDefaultUriMatchAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.DefaultUriMatchKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetDefaultUriMatchAsync(int? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.DefaultUriMatchKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetNeverDomainsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.NeverDomainsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetNeverDomainsAsync(HashSet value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.NeverDomainsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetClearClipboardAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ClearClipboardKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetClearClipboardAsync(int? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ClearClipboardKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedCollectionsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.CollectionsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedCollectionsAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.CollectionsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPasswordRepromptAutofillAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetPasswordRepromptAutofillAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordRepromptAutofillKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPasswordVerifiedAutofillAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetPasswordVerifiedAutofillAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PasswordVerifiedAutofillKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetLastSyncAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LastSyncKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetLastSyncAsync(DateTime? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.LastSyncKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetSecurityStampAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.Stamp; + } + + public async Task SetSecurityStampAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.Stamp = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetEmailVerifiedAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Profile?.EmailVerified ?? false; + } + + public async Task SetEmailVerifiedAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.EmailVerified = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetSyncOnRefreshAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.SyncOnRefreshKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetSyncOnRefreshAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.SyncOnRefreshKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetRememberedEmailAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.RememberedEmailKey; + return await GetValueAsync(key, options); + } + + public async Task SetRememberedEmailAsync(string value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.RememberedEmailKey; + await SetValueAsync(key, value, options); + } + + public async Task GetRememberedOrgIdentifierAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.RememberedOrgIdentifierKey; + return await GetValueAsync(key, options); + } + + public async Task SetRememberedOrgIdentifierAsync(string value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.RememberedOrgIdentifierKey; + await SetValueAsync(key, value, options); + } + + public async Task GetThemeAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ThemeKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetThemeAsync(string value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ThemeKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task ApplyThemeGloballyAsync(string value) + { + // TODO remove this method (ApplyThemeGlobally) to restore per-account theme support + await CheckStateAsync(); + if (_state?.Accounts == null) + { + return; + } + var activeUserId = await GetActiveUserIdAsync(); + foreach (var account in _state.Accounts) + { + var uid = account.Value?.Profile?.UserId; + // skip active user (theme already set) + if (uid != null && uid != activeUserId) + { + await SetThemeAsync(value, uid); + } + } + } + + public async Task GetAddSitePromptShownAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.AddSitePromptShownKey; + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetAddSitePromptShownAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.AddSitePromptShownKey; + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPushInitialPromptShownAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushInitialPromptShownKey; + return await GetValueAsync(key, options); + } + + public async Task SetPushInitialPromptShownAsync(bool? value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushInitialPromptShownKey; + await SetValueAsync(key, value, options); + } + + public async Task GetPushLastRegistrationDateAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushLastRegistrationDateKey; + return await GetValueAsync(key, options); + } + + public async Task SetPushLastRegistrationDateAsync(DateTime? value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushLastRegistrationDateKey; + await SetValueAsync(key, value, options); + } + + public async Task GetPushInstallationRegistrationErrorAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushInstallationRegistrationErrorKey; + return await GetValueAsync(key, options); + } + + public async Task SetPushInstallationRegistrationErrorAsync(string value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushInstallationRegistrationErrorKey; + await SetValueAsync(key, value, options); + } + + public async Task GetPushCurrentTokenAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushCurrentTokenKey; + return await GetValueAsync(key, options); + } + + public async Task SetPushCurrentTokenAsync(string value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushCurrentTokenKey; + await SetValueAsync(key, value, options); + } + + public async Task> GetEventCollectionAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.EventCollectionKey; + return await GetValueAsync>(key, options); + } + + public async Task SetEventCollectionAsync(List value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.EventCollectionKey; + await SetValueAsync(key, value, options); + } + + public async Task> GetEncryptedFoldersAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.FoldersKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedFoldersAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.FoldersKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedPoliciesAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PoliciesKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedPoliciesAsync(Dictionary value, string userId) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PoliciesKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPushRegisteredTokenAsync() + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushRegisteredTokenKey; + return await GetValueAsync(key, options); + } + + public async Task SetPushRegisteredTokenAsync(string value) + { + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.PushRegisteredTokenKey; + await SetValueAsync(key, value, options); + } + + public async Task GetUsesKeyConnectorAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.UsesKeyConnectorKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? false; + } + + public async Task SetUsesKeyConnectorAsync(bool? value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.UsesKeyConnectorKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetOrganizationsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.OrganizationsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetOrganizationsAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.OrganizationsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetPasswordGenerationOptionsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenOptionsKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetPasswordGenerationOptionsAsync(PasswordGenerationOptions value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenOptionsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedPasswordGenerationHistory(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenHistoryKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedPasswordGenerationHistoryAsync(List value, + string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.PassGenHistoryKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetEncryptedSendsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.SendsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetEncryptedSendsAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.SendsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task> GetSettingsAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.SettingsKey(reconciledOptions.UserId); + return await GetValueAsync>(key, reconciledOptions); + } + + public async Task SetSettingsAsync(Dictionary value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.SettingsKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); + } + + public async Task GetAccessTokenAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Tokens?.AccessToken; + } + + public async Task SetAccessTokenAsync(string value, bool skipTokenStorage, string userId = null) + { + var reconciledOptions = ReconcileOptions( + new StorageOptions { UserId = userId, SkipTokenStorage = skipTokenStorage }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Tokens.AccessToken = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetRefreshTokenAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) + ))?.Tokens?.RefreshToken; + } + + public async Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null) + { + var reconciledOptions = ReconcileOptions( + new StorageOptions { UserId = userId, SkipTokenStorage = skipTokenStorage }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Tokens.RefreshToken = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetTwoFactorTokenAsync(string email = null) + { + var reconciledOptions = + ReconcileOptions(new StorageOptions { Email = email }, await GetDefaultStorageOptionsAsync()); + var key = Constants.TwoFactorTokenKey(reconciledOptions.Email); + return await GetValueAsync(key, reconciledOptions); + } + + public async Task SetTwoFactorTokenAsync(string value, string email = null) + { + var reconciledOptions = + ReconcileOptions(new StorageOptions { Email = email }, await GetDefaultStorageOptionsAsync()); + var key = Constants.TwoFactorTokenKey(reconciledOptions.Email); + await SetValueAsync(key, value, reconciledOptions); + } + + // Helpers + + private async Task GetValueAsync(string key, StorageOptions options) + { + var value = await GetStorageService(options).GetAsync(key); + Log("GET", options, key, JsonConvert.SerializeObject(value)); + return value; + } + + private async Task SetValueAsync(string key, T value, StorageOptions options) + { + if (value == null) + { + Log("REMOVE", options, key, null); + await GetStorageService(options).RemoveAsync(key); + return; + } + Log("SET", options, key, JsonConvert.SerializeObject(value)); + await GetStorageService(options).SaveAsync(key, value); + } + + private IStorageService GetStorageService(StorageOptions options) + { + return options.UseSecureStorage.GetValueOrDefault(false) ? _secureStorageService : _storageService; + } + + private async Task GetAccountAsync(StorageOptions options) + { + await CheckStateAsync(); + + if (options?.UserId == null) + { + return null; + } + + // Memory + if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) + { + if (_state.Accounts[options.UserId].Keys == null) + { + _state.Accounts[options.UserId].Keys = new Account.AccountKeys(); + } + return _state.Accounts[options.UserId]; + } + + // Storage + _state = await GetStateFromStorageAsync(); + if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) + { + if (_state.Accounts[options.UserId].Keys == null) + { + _state.Accounts[options.UserId].Keys = new Account.AccountKeys(); + } + return _state.Accounts[options.UserId]; + } + + return null; + } + + private async Task SaveAccountAsync(Account account, StorageOptions options = null) + { + if (account?.Profile?.UserId == null) + { + throw new Exception("account?.Profile?.UserId cannot be null"); + } + + await CheckStateAsync(); + + // Memory + if (UseMemory(options)) + { + if (_state.Accounts == null) + { + _state.Accounts = new Dictionary(); + } + _state.Accounts[account.Profile.UserId] = account; + } + + // Storage + if (UseDisk(options)) + { + var state = await GetStateFromStorageAsync() ?? new State(); + if (state.Accounts == null) + { + state.Accounts = new Dictionary(); + } + + // Use Account copy constructor to clone with keys excluded (for storage) + state.Accounts[account.Profile.UserId] = new Account(account); + + // If we have a vault timeout and the action is log out, don't store token + if (options?.SkipTokenStorage.GetValueOrDefault() ?? false) + { + state.Accounts[account.Profile.UserId].Tokens.AccessToken = null; + state.Accounts[account.Profile.UserId].Tokens.RefreshToken = null; + } + + await SaveStateToStorageAsync(state); + } + } + + private async Task RemoveAccountAsync(string userId, bool userInitiated) + { + if (string.IsNullOrWhiteSpace(userId)) + { + throw new ArgumentNullException(nameof(userId)); + } + + var email = await GetEmailAsync(userId); + + // Memory + if (_state?.Accounts?.ContainsKey(userId) ?? false) + { + if (userInitiated) + { + _state.Accounts.Remove(userId); + } + else + { + _state.Accounts[userId].Tokens.AccessToken = null; + _state.Accounts[userId].Tokens.RefreshToken = null; + _state.Accounts[userId].Keys.Key = null; + } + } + if (userInitiated && _state?.ActiveUserId == userId) + { + _state.ActiveUserId = null; + } + + // Storage + var stateModified = false; + var state = await GetStateFromStorageAsync(); + if (state?.Accounts?.ContainsKey(userId) ?? false) + { + if (userInitiated) + { + state.Accounts.Remove(userId); + } + else + { + state.Accounts[userId].Tokens.AccessToken = null; + state.Accounts[userId].Tokens.RefreshToken = null; + } + stateModified = true; + } + if (userInitiated && state?.ActiveUserId == userId) + { + state.ActiveUserId = null; + stateModified = true; + } + if (stateModified) + { + await SaveStateToStorageAsync(state); + } + + // Non-state storage + await SetBiometricUnlockAsync(null, userId); + await SetProtectedPinAsync(null, userId); + await SetPinProtectedAsync(null, userId); + await SetKeyEncryptedAsync(null, userId); + await SetKeyHashAsync(null, userId); + await SetEncKeyEncryptedAsync(null, userId); + await SetOrgKeysEncryptedAsync(null, userId); + await SetPrivateKeyEncryptedAsync(null, userId); + await SetLastActiveTimeAsync(null, userId); + await SetLastFileCacheClearAsync(null, userId); + await SetPreviousPageInfoAsync(null, userId); + await SetInvalidUnlockAttemptsAsync(null, userId); + await SetLocalDataAsync(null, userId); + await SetEncryptedCiphersAsync(null, userId); + await SetEncryptedCollectionsAsync(null, userId); + await SetLastSyncAsync(null, userId); + await SetEncryptedFoldersAsync(null, userId); + await SetEncryptedPoliciesAsync(null, userId); + await SetUsesKeyConnectorAsync(null, userId); + await SetOrganizationsAsync(null, userId); + await SetEncryptedPasswordGenerationHistoryAsync(null, userId); + await SetEncryptedSendsAsync(null, userId); + await SetSettingsAsync(null, userId); + if (!string.IsNullOrWhiteSpace(email)) + { + await SetTwoFactorTokenAsync(null, email); + } + + if (userInitiated) + { + // user initiated logout (not vault timeout or scaffolding new account) so remove remaining settings + await SetAutofillBlacklistedUrisAsync(null, userId); + await SetDisableFaviconAsync(null, userId); + await SetDisableAutoTotpCopyAsync(null, userId); + await SetInlineAutofillEnabledAsync(null, userId); + await SetAutofillDisableSavePromptAsync(null, userId); + await SetDefaultUriMatchAsync(null, userId); + await SetNeverDomainsAsync(null, userId); + await SetClearClipboardAsync(null, userId); + await SetPasswordRepromptAutofillAsync(null, userId); + await SetPasswordVerifiedAutofillAsync(null, userId); + await SetSyncOnRefreshAsync(null, userId); + await SetThemeAsync(null, userId); + await SetAddSitePromptShownAsync(null, userId); + await SetPasswordGenerationOptionsAsync(null, userId); + } + } + + private async Task ScaffoldNewAccountAsync(Account account) + { + await CheckStateAsync(); + var currentTheme = await GetThemeAsync(); + + account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); + + // Storage + var state = await GetStateFromStorageAsync() ?? new State(); + if (state.Accounts == null) + { + state.Accounts = new Dictionary(); + } + if (state.Accounts.ContainsKey(account.Profile.UserId)) + { + // Run cleanup pass on existing account before proceeding + await RemoveAccountAsync(account.Profile.UserId, false); + var existingAccount = state.Accounts[account.Profile.UserId]; + account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout; + account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction; + } + + // New account defaults + if (account.Settings.VaultTimeout == null) + { + account.Settings.VaultTimeout = 15; + } + if (account.Settings.VaultTimeoutAction == null) + { + account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock; + } + await SetThemeAsync(currentTheme, account.Profile.UserId); + + state.Accounts[account.Profile.UserId] = account; + await SaveStateToStorageAsync(state); + + // Memory + if (_state == null) + { + _state = state; + } + else + { + if (_state.Accounts == null) + { + _state.Accounts = new Dictionary(); + } + _state.Accounts[account.Profile.UserId] = account; + } + } + + private StorageOptions ReconcileOptions(StorageOptions requestedOptions, StorageOptions defaultOptions) + { + if (requestedOptions == null) + { + return defaultOptions; + } + requestedOptions.StorageLocation = requestedOptions.StorageLocation ?? defaultOptions.StorageLocation; + requestedOptions.UseSecureStorage = requestedOptions.UseSecureStorage ?? defaultOptions.UseSecureStorage; + requestedOptions.UserId = requestedOptions.UserId ?? defaultOptions.UserId; + requestedOptions.Email = requestedOptions.Email ?? defaultOptions.Email; + requestedOptions.SkipTokenStorage = requestedOptions.SkipTokenStorage ?? defaultOptions.SkipTokenStorage; + return requestedOptions; + } + + private async Task GetDefaultStorageOptionsAsync() + { + return new StorageOptions() + { + StorageLocation = StorageLocation.Both, + UserId = await GetActiveUserIdAsync(), + }; + } + + private async Task GetDefaultSecureStorageOptionsAsync() + { + return new StorageOptions() + { + StorageLocation = StorageLocation.Disk, + UseSecureStorage = true, + UserId = await GetActiveUserIdAsync(), + }; + } + + private async Task GetDefaultInMemoryOptionsAsync() + { + return new StorageOptions() + { + StorageLocation = StorageLocation.Memory, + UserId = await GetActiveUserIdAsync(), + }; + } + + private bool UseMemory(StorageOptions options) + { + return options?.StorageLocation == StorageLocation.Memory || + options?.StorageLocation == StorageLocation.Both; + } + + private bool UseDisk(StorageOptions options) + { + return options?.StorageLocation == StorageLocation.Disk || + options?.StorageLocation == StorageLocation.Both; + } + + private async Task GetStateFromStorageAsync() + { + var state = await _storageService.GetAsync(Constants.StateKey); + // TODO Remove logging once all bugs are squished + Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), + ">>> GetStateFromStorageAsync()"); + return state; + } + + private async Task SaveStateToStorageAsync(State state) + { + await _storageService.SaveAsync(Constants.StateKey, state); + // TODO Remove logging once all bugs are squished + Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), + ">>> SaveStateToStorageAsync()"); + } + + private async Task CheckStateAsync() + { + if (!_migrationChecked) + { + var migrationService = ServiceContainer.Resolve("stateMigrationService"); + if (await migrationService.NeedsMigration()) + { + await migrationService.Migrate(); + } + _migrationChecked = true; + } + + if (_state == null) + { + _state = await GetStateFromStorageAsync() ?? new State(); + } + } + + private async Task ValidateUserAsync(string userId) + { + if (string.IsNullOrWhiteSpace(userId)) + { + throw new ArgumentNullException(nameof(userId)); + } + await CheckStateAsync(); + var accounts = _state?.Accounts; + if (accounts == null || !accounts.Any()) + { + throw new Exception("At least one account required to validate user"); + } + foreach (var account in accounts) + { + if (account.Key == userId) + { + // found match, user is valid + return; + } + } + throw new Exception("User does not exist in account list"); + } + + private void Log(string tag, StorageOptions options, string key, string value) + { + // TODO Remove this once all bugs are squished + var text = options?.UseSecureStorage ?? false ? "SECURE / " : ""; + text += "Key: " + key + " / "; + if (value != null) + { + text += "Value: " + value; + } + Debug.WriteLine(text, ">>> " + tag); } } } diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs index 7d7795a4e..9f339bbb4 100644 --- a/src/Core/Services/SyncService.cs +++ b/src/Core/Services/SyncService.cs @@ -12,45 +12,43 @@ namespace Bit.Core.Services { public class SyncService : ISyncService { - private const string Keys_LastSyncFormat = "lastSync_{0}"; - - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IApiService _apiService; private readonly ISettingsService _settingsService; private readonly IFolderService _folderService; private readonly ICipherService _cipherService; private readonly ICryptoService _cryptoService; private readonly ICollectionService _collectionService; - private readonly IStorageService _storageService; + private readonly IOrganizationService _organizationService; private readonly IMessagingService _messagingService; private readonly IPolicyService _policyService; private readonly ISendService _sendService; private readonly IKeyConnectorService _keyConnectorService; - private readonly Func _logoutCallbackAsync; + private readonly Func, Task> _logoutCallbackAsync; public SyncService( - IUserService userService, + IStateService stateService, IApiService apiService, ISettingsService settingsService, IFolderService folderService, ICipherService cipherService, ICryptoService cryptoService, ICollectionService collectionService, - IStorageService storageService, + IOrganizationService organizationService, IMessagingService messagingService, IPolicyService policyService, ISendService sendService, IKeyConnectorService keyConnectorService, - Func logoutCallbackAsync) + Func, Task> logoutCallbackAsync) { - _userService = userService; + _stateService = stateService; _apiService = apiService; _settingsService = settingsService; _folderService = folderService; _cipherService = cipherService; _cryptoService = cryptoService; _collectionService = collectionService; - _storageService = storageService; + _organizationService = organizationService; _messagingService = messagingService; _policyService = policyService; _sendService = sendService; @@ -62,28 +60,26 @@ namespace Bit.Core.Services public async Task GetLastSyncAsync() { - var userId = await _userService.GetUserIdAsync(); - if (userId == null) + if (await _stateService.GetActiveUserIdAsync() == null) { return null; } - return await _storageService.GetAsync(string.Format(Keys_LastSyncFormat, userId)); + return await _stateService.GetLastSyncAsync(); } public async Task SetLastSyncAsync(DateTime date) { - var userId = await _userService.GetUserIdAsync(); - if (userId == null) + if (await _stateService.GetActiveUserIdAsync() == null) { return; } - await _storageService.SaveAsync(string.Format(Keys_LastSyncFormat, userId), date); + await _stateService.SetLastSyncAsync(date); } public async Task FullSyncAsync(bool forceSync, bool allowThrowOnError = false) { SyncStarted(); - var isAuthenticated = await _userService.IsAuthenticatedAsync(); + var isAuthenticated = await _stateService.IsAuthenticatedAsync(); if (!isAuthenticated) { return SyncCompleted(false); @@ -101,7 +97,7 @@ namespace Bit.Core.Services await SetLastSyncAsync(now); return SyncCompleted(false); } - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); try { var response = await _apiService.GetSyncAsync(); @@ -131,7 +127,7 @@ namespace Bit.Core.Services public async Task SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { try { @@ -142,7 +138,7 @@ namespace Bit.Core.Services var remoteFolder = await _apiService.GetFolderAsync(notification.Id); if (remoteFolder != null) { - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await _folderService.UpsertAsync(new FolderData(remoteFolder, userId)); _messagingService.Send("syncedUpsertedFolder", new Dictionary { @@ -160,7 +156,7 @@ namespace Bit.Core.Services public async Task SyncDeleteFolderAsync(SyncFolderNotification notification) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { await _folderService.DeleteAsync(notification.Id); _messagingService.Send("syncedDeletedFolder", new Dictionary @@ -175,7 +171,7 @@ namespace Bit.Core.Services public async Task SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { try { @@ -230,7 +226,7 @@ namespace Bit.Core.Services var remoteCipher = await _apiService.GetCipherAsync(notification.Id); if (remoteCipher != null) { - var userId = await _userService.GetUserIdAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); await _cipherService.UpsertAsync(new CipherData(remoteCipher, userId)); _messagingService.Send("syncedUpsertedCipher", new Dictionary { @@ -259,7 +255,7 @@ namespace Bit.Core.Services public async Task SyncDeleteCipherAsync(SyncCipherNotification notification) { SyncStarted(); - if (await _userService.IsAuthenticatedAsync()) + if (await _stateService.IsAuthenticatedAsync()) { await _cipherService.DeleteAsync(notification.Id); _messagingService.Send("syncedDeletedCipher", new Dictionary @@ -315,23 +311,22 @@ namespace Bit.Core.Services private async Task SyncProfileAsync(ProfileResponse response) { - var stamp = await _userService.GetSecurityStampAsync(); + var stamp = await _stateService.GetSecurityStampAsync(); if (stamp != null && stamp != response.SecurityStamp) { if (_logoutCallbackAsync != null) { - await _logoutCallbackAsync(true); + await _logoutCallbackAsync(new Tuple(response.Id, false, true)); } return; } await _cryptoService.SetEncKeyAsync(response.Key); await _cryptoService.SetEncPrivateKeyAsync(response.PrivateKey); await _cryptoService.SetOrgKeysAsync(response.Organizations); - await _userService.SetSecurityStampAsync(response.SecurityStamp); + await _stateService.SetSecurityStampAsync(response.SecurityStamp); var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o)); - await _userService.ReplaceOrganizationsAsync(organizations); - await _userService.SetEmailVerifiedAsync(response.EmailVerified); - await _userService.SetForcePasswordReset(response.ForcePasswordReset); + await _organizationService.ReplaceAsync(organizations); + await _stateService.SetEmailVerifiedAsync(response.EmailVerified); await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector); } diff --git a/src/Core/Services/TokenService.cs b/src/Core/Services/TokenService.cs index 09f1c44cc..817cf6ad4 100644 --- a/src/Core/Services/TokenService.cs +++ b/src/Core/Services/TokenService.cs @@ -5,132 +5,104 @@ using System; using System.Text; using System.Threading.Tasks; using System.Linq; +using Bit.Core.Enums; namespace Bit.Core.Services { public class TokenService : ITokenService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; - private string _token; - private JObject _decodedToken; - private string _refreshToken; + private string _accessTokenForDecoding; + private JObject _decodedAccessToken; - private const string Keys_AccessToken = "accessToken"; - private const string Keys_RefreshToken = "refreshToken"; - private const string Keys_TwoFactorTokenFormat = "twoFactorToken_{0}"; - - public TokenService(IStorageService storageService) + public TokenService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; } public async Task SetTokensAsync(string accessToken, string refreshToken) { await Task.WhenAll( - SetTokenAsync(accessToken), + SetAccessTokenAsync(accessToken), SetRefreshTokenAsync(refreshToken)); } - public async Task SetTokenAsync(string token) + public async Task SetAccessTokenAsync(string accessToken, bool forDecodeOnly = false) { - _token = token; - _decodedToken = null; - - if (await SkipTokenStorage()) + _accessTokenForDecoding = accessToken; + _decodedAccessToken = null; + if (!forDecodeOnly) { - // If we have a vault timeout and the action is log out, don't store token - return; + await _stateService.SetAccessTokenAsync(accessToken, await SkipTokenStorage()); } - - await _storageService.SaveAsync(Keys_AccessToken, token); } public async Task GetTokenAsync() { - if (_token != null) - { - return _token; - } - _token = await _storageService.GetAsync(Keys_AccessToken); - return _token; + _accessTokenForDecoding = await _stateService.GetAccessTokenAsync(); + return _accessTokenForDecoding; } public async Task SetRefreshTokenAsync(string refreshToken) { - _refreshToken = refreshToken; - - if (await SkipTokenStorage()) - { - // If we have a vault timeout and the action is log out, don't store token - return; - } - - await _storageService.SaveAsync(Keys_RefreshToken, refreshToken); + await _stateService.SetRefreshTokenAsync(refreshToken, await SkipTokenStorage()); } public async Task GetRefreshTokenAsync() { - if (_refreshToken != null) - { - return _refreshToken; - } - _refreshToken = await _storageService.GetAsync(Keys_RefreshToken); - return _refreshToken; + return await _stateService.GetRefreshTokenAsync(); } public async Task ToggleTokensAsync() { + // load and re-save tokens to reflect latest value of SkipTokenStorage() var token = await GetTokenAsync(); var refreshToken = await GetRefreshTokenAsync(); - if (await SkipTokenStorage()) - { - await ClearTokenAsync(); - _token = token; - _refreshToken = refreshToken; - return; - } - - await SetTokenAsync(token); + await SetAccessTokenAsync(token); await SetRefreshTokenAsync(refreshToken); } public async Task SetTwoFactorTokenAsync(string token, string email) { - await _storageService.SaveAsync(string.Format(Keys_TwoFactorTokenFormat, email), token); + await _stateService.SetTwoFactorTokenAsync(token, email); } public async Task GetTwoFactorTokenAsync(string email) { - return await _storageService.GetAsync(string.Format(Keys_TwoFactorTokenFormat, email)); + return await _stateService.GetTwoFactorTokenAsync(email); } public async Task ClearTwoFactorTokenAsync(string email) { - await _storageService.RemoveAsync(string.Format(Keys_TwoFactorTokenFormat, email)); + await _stateService.SetTwoFactorTokenAsync(null, email); } - public async Task ClearTokenAsync() + public async Task ClearTokenAsync(string userId = null) { - _token = null; - _decodedToken = null; - _refreshToken = null; + ClearCache(); await Task.WhenAll( - _storageService.RemoveAsync(Keys_AccessToken), - _storageService.RemoveAsync(Keys_RefreshToken)); + _stateService.SetAccessTokenAsync(null, false, userId), + _stateService.SetRefreshTokenAsync(null, false, userId)); + } + + public void ClearCache() + { + _accessTokenForDecoding = null; + _decodedAccessToken = null; } public JObject DecodeToken() { - if (_decodedToken != null) + if (_decodedAccessToken != null) { - return _decodedToken; + return _decodedAccessToken; } - if (_token == null) + if (_accessTokenForDecoding == null) { - throw new InvalidOperationException("Token not found."); + throw new InvalidOperationException("Access token not found."); } - var parts = _token.Split('.'); + var parts = _accessTokenForDecoding.Split('.'); if (parts.Length != 3) { throw new InvalidOperationException("JWT must have 3 parts."); @@ -140,8 +112,8 @@ namespace Bit.Core.Services { throw new InvalidOperationException("Cannot decode the token."); } - _decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes)); - return _decodedToken; + _decodedAccessToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes)); + return _decodedAccessToken; } public DateTime? GetTokenExpirationDate() @@ -231,8 +203,12 @@ namespace Bit.Core.Services return decoded["iss"].Value(); } - public bool GetIsExternal() + public async Task GetIsExternal() { + if (_accessTokenForDecoding == null) + { + await GetTokenAsync(); + } var decoded = DecodeToken(); if (decoded?["amr"] == null) { @@ -243,9 +219,9 @@ namespace Bit.Core.Services private async Task SkipTokenStorage() { - var timeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); - var action = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); - return timeout.HasValue && action == "logOut"; + var timeout = await _stateService.GetVaultTimeoutAsync(); + var action = await _stateService.GetVaultTimeoutActionAsync(); + return timeout.HasValue && action == VaultTimeoutAction.Logout; } } } diff --git a/src/Core/Services/TotpService.cs b/src/Core/Services/TotpService.cs index 0cf4dce15..07c64285a 100644 --- a/src/Core/Services/TotpService.cs +++ b/src/Core/Services/TotpService.cs @@ -10,14 +10,14 @@ namespace Bit.Core.Services { private const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY"; - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; public TotpService( - IStorageService storageService, + IStateService stateService, ICryptoFunctionService cryptoFunctionService) { - _storageService = storageService; + _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; } @@ -135,7 +135,7 @@ namespace Bit.Core.Services public async Task IsAutoCopyEnabledAsync() { - var disabled = await _storageService.GetAsync(Constants.DisableAutoTotpCopyKey); + var disabled = await _stateService.GetDisableAutoTotpCopyAsync(); return !disabled.GetValueOrDefault(); } } diff --git a/src/Core/Services/UserService.cs b/src/Core/Services/UserService.cs deleted file mode 100644 index c71e52e07..000000000 --- a/src/Core/Services/UserService.cs +++ /dev/null @@ -1,221 +0,0 @@ -using Bit.Core.Abstractions; -using Bit.Core.Enums; -using Bit.Core.Models.Data; -using Bit.Core.Models.Domain; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Bit.Core.Services -{ - public class UserService : IUserService - { - private string _userId; - private string _email; - private string _stamp; - private KdfType? _kdf; - private int? _kdfIterations; - private bool? _emailVerified; - private bool? _forcePasswordReset; - - private const string Keys_UserId = "userId"; - private const string Keys_UserEmail = "userEmail"; - private const string Keys_Stamp = "securityStamp"; - private const string Keys_Kdf = "kdf"; - private const string Keys_KdfIterations = "kdfIterations"; - private const string Keys_OrganizationsFormat = "organizations_{0}"; - private const string Keys_EmailVerified = "emailVerified"; - private const string Keys_ForcePasswordReset = "forcePasswordReset"; - - private readonly IStorageService _storageService; - private readonly ITokenService _tokenService; - - public UserService(IStorageService storageService, ITokenService tokenService) - { - _storageService = storageService; - _tokenService = tokenService; - } - - public async Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations) - { - _email = email; - _userId = userId; - _kdf = kdf; - _kdfIterations = kdfIterations; - await Task.WhenAll( - _storageService.SaveAsync(Keys_UserEmail, email), - _storageService.SaveAsync(Keys_UserId, userId), - _storageService.SaveAsync(Keys_Kdf, (int)kdf), - _storageService.SaveAsync(Keys_KdfIterations, kdfIterations)); - } - - public async Task SetSecurityStampAsync(string stamp) - { - _stamp = stamp; - await _storageService.SaveAsync(Keys_Stamp, stamp); - } - - public async Task SetEmailVerifiedAsync(bool emailVerified) - { - _emailVerified = emailVerified; - await _storageService.SaveAsync(Keys_EmailVerified, emailVerified); - } - - public async Task SetForcePasswordReset(bool forcePasswordReset) - { - _forcePasswordReset = forcePasswordReset; - await _storageService.SaveAsync(Keys_ForcePasswordReset, forcePasswordReset); - } - - public async Task GetUserIdAsync() - { - if (_userId == null) - { - _userId = await _storageService.GetAsync(Keys_UserId); - } - return _userId; - } - - public async Task GetEmailAsync() - { - if (_email == null) - { - _email = await _storageService.GetAsync(Keys_UserEmail); - } - return _email; - } - - public async Task GetSecurityStampAsync() - { - if (_stamp == null) - { - _stamp = await _storageService.GetAsync(Keys_Stamp); - } - return _stamp; - } - - public async Task GetEmailVerifiedAsync() - { - if (_emailVerified == null) - { - _emailVerified = await _storageService.GetAsync(Keys_EmailVerified); - } - return _emailVerified.GetValueOrDefault(); - } - - public async Task GetKdfAsync() - { - if (_kdf == null) - { - _kdf = (KdfType?)(await _storageService.GetAsync(Keys_Kdf)); - } - return _kdf; - } - - public async Task GetKdfIterationsAsync() - { - if (_kdfIterations == null) - { - _kdfIterations = await _storageService.GetAsync(Keys_KdfIterations); - } - return _kdfIterations; - } - - public async Task GetForcePasswordReset() - { - if (_forcePasswordReset == null) - { - _forcePasswordReset = await _storageService.GetAsync(Keys_ForcePasswordReset); - } - return _forcePasswordReset.GetValueOrDefault(); - } - - public async Task ClearAsync() - { - var userId = await GetUserIdAsync(); - await Task.WhenAll( - _storageService.RemoveAsync(Keys_UserId), - _storageService.RemoveAsync(Keys_UserEmail), - _storageService.RemoveAsync(Keys_Stamp), - _storageService.RemoveAsync(Keys_Kdf), - _storageService.RemoveAsync(Keys_KdfIterations), - _storageService.RemoveAsync(Keys_ForcePasswordReset), - ClearOrganizationsAsync(userId)); - _userId = _email = _stamp = null; - _kdf = null; - _kdfIterations = null; - } - - public async Task IsAuthenticatedAsync() - { - var token = await _tokenService.GetTokenAsync(); - if (token == null) - { - return false; - } - var userId = await GetUserIdAsync(); - return userId != null; - } - - public async Task CanAccessPremiumAsync() - { - var authed = await IsAuthenticatedAsync(); - if (!authed) - { - return false; - } - - var tokenPremium = _tokenService.GetPremium(); - if (tokenPremium) - { - return true; - } - var orgs = await GetAllOrganizationAsync(); - return orgs?.Any(o => o.UsersGetPremium && o.Enabled) ?? false; - } - - public async Task GetOrganizationAsync(string id) - { - var userId = await GetUserIdAsync(); - var organizations = await _storageService.GetAsync>( - string.Format(Keys_OrganizationsFormat, userId)); - if (organizations == null || !organizations.ContainsKey(id)) - { - return null; - } - return new Organization(organizations[id]); - } - - public async Task GetOrganizationByIdentifierAsync(string identifier) - { - var userId = await GetUserIdAsync(); - var organizations = await GetAllOrganizationAsync(); - - if (organizations == null || organizations.Count == 0) - { - return null; - } - - return organizations.FirstOrDefault(o => o.Identifier == identifier); - } - - public async Task> GetAllOrganizationAsync() - { - var userId = await GetUserIdAsync(); - var organizations = await _storageService.GetAsync>( - string.Format(Keys_OrganizationsFormat, userId)); - return organizations?.Select(o => new Organization(o.Value)).ToList() ?? new List(); - } - - public async Task ReplaceOrganizationsAsync(Dictionary organizations) - { - var userId = await GetUserIdAsync(); - await _storageService.SaveAsync(string.Format(Keys_OrganizationsFormat, userId), organizations); - } - - public async Task ClearOrganizationsAsync(string userId) - { - await _storageService.RemoveAsync(string.Format(Keys_OrganizationsFormat, userId)); - } - } -} diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index b6768e602..692cd3ccd 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -1,5 +1,4 @@ using Bit.Core.Abstractions; -using Bit.Core.Models.Domain; using System; using System.Linq; using System.Threading.Tasks; @@ -10,9 +9,8 @@ namespace Bit.Core.Services public class VaultTimeoutService : IVaultTimeoutService { private readonly ICryptoService _cryptoService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private readonly IPlatformUtilsService _platformUtilsService; - private readonly IStorageService _storageService; private readonly IFolderService _folderService; private readonly ICipherService _cipherService; private readonly ICollectionService _collectionService; @@ -22,13 +20,12 @@ namespace Bit.Core.Services private readonly IPolicyService _policyService; private readonly IKeyConnectorService _keyConnectorService; private readonly Action _lockedCallback; - private readonly Func _loggedOutCallback; + private readonly Func, Task> _loggedOutCallback; public VaultTimeoutService( ICryptoService cryptoService, - IUserService userService, + IStateService stateService, IPlatformUtilsService platformUtilsService, - IStorageService storageService, IFolderService folderService, ICipherService cipherService, ICollectionService collectionService, @@ -38,12 +35,11 @@ namespace Bit.Core.Services IPolicyService policyService, IKeyConnectorService keyConnectorService, Action lockedCallback, - Func loggedOutCallback) + Func, Task> loggedOutCallback) { _cryptoService = cryptoService; - _userService = userService; + _stateService = stateService; _platformUtilsService = platformUtilsService; - _storageService = storageService; _folderService = folderService; _cipherService = cipherService; _collectionService = collectionService; @@ -56,23 +52,28 @@ namespace Bit.Core.Services _loggedOutCallback = loggedOutCallback; } - public EncString PinProtectedKey { get; set; } = null; - public bool BiometricLocked { get; set; } = true; public long? DelayLockAndLogoutMs { get; set; } - public async Task IsLockedAsync() + public async Task IsLockedAsync(string userId = null) { - var hasKey = await _cryptoService.HasKeyAsync(); + var hasKey = await _cryptoService.HasKeyAsync(userId); if (hasKey) { - var biometricSet = await IsBiometricLockSetAsync(); - if (biometricSet && BiometricLocked) + var biometricSet = await IsBiometricLockSetAsync(userId); + if (biometricSet && _stateService.BiometricLocked) { return true; } } return !hasKey; } + + public async Task IsLoggedOutByTimeoutAsync(string userId = null) + { + var authed = await _stateService.IsAuthenticatedAsync(userId); + var email = await _stateService.GetEmailAsync(userId); + return !authed && !string.IsNullOrWhiteSpace(email); + } public async Task CheckVaultTimeoutAsync() { @@ -80,79 +81,85 @@ namespace Bit.Core.Services { return; } - var authed = await _userService.IsAuthenticatedAsync(); + + if (await ShouldTimeoutAsync()) + { + await ExecuteTimeoutActionAsync(); + } + } + + public async Task ShouldTimeoutAsync(string userId = null) + { + var authed = await _stateService.IsAuthenticatedAsync(userId); if (!authed) { - return; + return false; } - if (await IsLockedAsync()) + var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync(userId); + if (vaultTimeoutAction == VaultTimeoutAction.Lock && await IsLockedAsync(userId)) { - return; + return false; } - var vaultTimeoutMinutes = await GetVaultTimeout(); + var vaultTimeoutMinutes = await GetVaultTimeout(userId); if (vaultTimeoutMinutes < 0 || vaultTimeoutMinutes == null) { - return; + return false; } if (vaultTimeoutMinutes == 0 && !DelayLockAndLogoutMs.HasValue) { - await LockOrLogout(); + return true; } - var lastActiveTime = await _storageService.GetAsync(Constants.LastActiveTimeKey); + var lastActiveTime = await _stateService.GetLastActiveTimeAsync(userId); if (lastActiveTime == null) { - return; + return false; } var diffMs = _platformUtilsService.GetActiveTime() - lastActiveTime; if (DelayLockAndLogoutMs.HasValue && diffMs < DelayLockAndLogoutMs) { - return; + return false; } var vaultTimeoutMs = vaultTimeoutMinutes * 60000; - if (diffMs >= vaultTimeoutMs) - { - await LockOrLogout(); - } - + return diffMs >= vaultTimeoutMs; } - private async Task LockOrLogout() + public async Task ExecuteTimeoutActionAsync(string userId = null) { - // Pivot based on saved action - var action = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); - if (action == "logOut") + var action = await _stateService.GetVaultTimeoutActionAsync(userId); + if (action == VaultTimeoutAction.Logout) { - await LogOutAsync(); + await LogOutAsync(false, userId); } else { - await LockAsync(true); + await LockAsync(true, false, userId); } } - public async Task LockAsync(bool allowSoftLock = false, bool userInitiated = false) + public async Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null) { - var authed = await _userService.IsAuthenticatedAsync(); + var authed = await _stateService.IsAuthenticatedAsync(userId); if (!authed) { return; } if (await _keyConnectorService.GetUsesKeyConnector()) { - var pinSet = await IsPinLockSetAsync(); - var pinLock = (pinSet.Item1 && PinProtectedKey != null) || pinSet.Item2; + var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId); + var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) || + isPinProtectedWithKey; if (!pinLock && !await IsBiometricLockSetAsync()) { - await LogOutAsync(); + await LogOutAsync(userInitiated, userId); return; } } if (allowSoftLock) { - BiometricLocked = await IsBiometricLockSetAsync(); - if (BiometricLocked) + _stateService.BiometricLocked = await IsBiometricLockSetAsync(); + if (_stateService.BiometricLocked) { _messagingService.Send("locked", userInitiated); _lockedCallback?.Invoke(userInitiated); @@ -160,10 +167,10 @@ namespace Bit.Core.Services } } await Task.WhenAll( - _cryptoService.ClearKeyAsync(), - _cryptoService.ClearOrgKeysAsync(true), - _cryptoService.ClearKeyPairAsync(true), - _cryptoService.ClearEncKeyAsync(true)); + _cryptoService.ClearKeyAsync(userId), + _cryptoService.ClearOrgKeysAsync(true, userId), + _cryptoService.ClearKeyPairAsync(true, userId), + _cryptoService.ClearEncKeyAsync(true, userId)); _folderService.ClearCache(); await _cipherService.ClearCacheAsync(); @@ -173,46 +180,46 @@ namespace Bit.Core.Services _lockedCallback?.Invoke(userInitiated); } - public async Task LogOutAsync() + public async Task LogOutAsync(bool userInitiated = true, string userId = null) { if(_loggedOutCallback != null) { - await _loggedOutCallback.Invoke(false); + await _loggedOutCallback.Invoke(new Tuple(userId, userInitiated, false)); } } - public async Task SetVaultTimeoutOptionsAsync(int? timeout, string action) + public async Task SetVaultTimeoutOptionsAsync(int? timeout, VaultTimeoutAction? action) { - await _storageService.SaveAsync(Constants.VaultTimeoutKey, timeout); - await _storageService.SaveAsync(Constants.VaultTimeoutActionKey, action); + await _stateService.SetVaultTimeoutAsync(timeout); + await _stateService.SetVaultTimeoutActionAsync(action); await _cryptoService.ToggleKeyAsync(); await _tokenService.ToggleTokensAsync(); } - public async Task> IsPinLockSetAsync() + public async Task> IsPinLockSetAsync(string userId = null) { - var protectedPin = await _storageService.GetAsync(Constants.ProtectedPin); - var pinProtectedKey = await _storageService.GetAsync(Constants.PinProtectedKey); + var protectedPin = await _stateService.GetProtectedPinAsync(userId); + var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId); return new Tuple(protectedPin != null, pinProtectedKey != null); } - public async Task IsBiometricLockSetAsync() + public async Task IsBiometricLockSetAsync(string userId = null) { - var biometricLock = await _storageService.GetAsync(Constants.BiometricUnlockKey); + var biometricLock = await _stateService.GetBiometricUnlockAsync(userId); return biometricLock.GetValueOrDefault(); } - public async Task ClearAsync() + public async Task ClearAsync(string userId = null) { - PinProtectedKey = null; - await _storageService.RemoveAsync(Constants.ProtectedPin); + await _stateService.SetPinProtectedKeyAsync(null, userId); + await _stateService.SetProtectedPinAsync(null, userId); } - public async Task GetVaultTimeout() { - var vaultTimeout = await _storageService.GetAsync(Constants.VaultTimeoutKey); + public async Task GetVaultTimeout(string userId = null) { + var vaultTimeout = await _stateService.GetVaultTimeoutAsync(userId); - if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout)) { - var policy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout)).First(); + if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)) { + var policy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout, userId)).First(); // Remove negative values, and ensure it's smaller than maximum allowed value according to policy var policyTimeout = _policyService.GetPolicyInt(policy, "minutes"); if (!policyTimeout.HasValue) @@ -228,7 +235,7 @@ namespace Bit.Core.Services // We really shouldn't need to set the value here, but multiple services relies on this value being correct. if (vaultTimeout != timeout) { - await _storageService.SaveAsync(Constants.VaultTimeoutKey, timeout); + await _stateService.SetVaultTimeoutAsync(timeout, userId); } return timeout; diff --git a/src/Core/Utilities/ServiceContainer.cs b/src/Core/Utilities/ServiceContainer.cs index dcafe40c5..2f30b1795 100644 --- a/src/Core/Utilities/ServiceContainer.cs +++ b/src/Core/Utilities/ServiceContainer.cs @@ -22,64 +22,65 @@ namespace Bit.Core.Utilities var platformUtilsService = Resolve("platformUtilsService"); var storageService = Resolve("storageService"); - var secureStorageService = Resolve("secureStorageService"); + var stateService = Resolve("stateService"); var i18nService = Resolve("i18nService"); var messagingService = Resolve("messagingService"); var cryptoFunctionService = Resolve("cryptoFunctionService"); var cryptoService = Resolve("cryptoService"); SearchService searchService = null; - var stateService = new StateService(); - var tokenService = new TokenService(storageService); - var apiService = new ApiService(tokenService, platformUtilsService, (bool expired) => + var tokenService = new TokenService(stateService); + var apiService = new ApiService(tokenService, platformUtilsService, (extras) => { - messagingService.Send("logout", expired); + messagingService.Send("logout", extras); return Task.FromResult(0); }, customUserAgent); var appIdService = new AppIdService(storageService); - var userService = new UserService(storageService, tokenService); - var settingsService = new SettingsService(userService, storageService); + var organizationService = new OrganizationService(stateService); + var settingsService = new SettingsService(stateService); var fileUploadService = new FileUploadService(apiService); - var cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, - storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys); - var folderService = new FolderService(cryptoService, userService, apiService, storageService, - i18nService, cipherService); - var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService); - var sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService, - i18nService, cryptoFunctionService); + var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService, + fileUploadService, storageService, i18nService, () => searchService, clearCipherCacheKey, + allClearCipherCacheKeys); + var folderService = new FolderService(cryptoService, stateService, apiService, i18nService, cipherService); + var collectionService = new CollectionService(cryptoService, stateService, i18nService); + var sendService = new SendService(cryptoService, stateService, apiService, fileUploadService, i18nService, + cryptoFunctionService); searchService = new SearchService(cipherService, sendService); - var policyService = new PolicyService(storageService, userService); - var keyConnectorService = new KeyConnectorService(userService, cryptoService, storageService, tokenService, apiService); - var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService, - storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService, - policyService, keyConnectorService, null, (expired) => + var policyService = new PolicyService(stateService, organizationService); + var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService, + organizationService); + var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService, + folderService, cipherService, collectionService, searchService, messagingService, tokenService, + policyService, keyConnectorService, null, (extras) => { - messagingService.Send("logout", expired); + messagingService.Send("logout", extras); return Task.FromResult(0); }); - var syncService = new SyncService(userService, apiService, settingsService, folderService, - cipherService, cryptoService, collectionService, storageService, messagingService, policyService, sendService, - keyConnectorService, (bool expired) => + var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService, + cryptoService, collectionService, organizationService, messagingService, policyService, sendService, + keyConnectorService, (extras) => { - messagingService.Send("logout", expired); + messagingService.Send("logout", extras); return Task.FromResult(0); }); - var passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, + var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService); - var totpService = new TotpService(storageService, cryptoFunctionService); - var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, userService, tokenService, appIdService, - i18nService, platformUtilsService, messagingService, vaultTimeoutService, keyConnectorService); + var totpService = new TotpService(stateService, cryptoFunctionService); + var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, + tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, + keyConnectorService); var exportService = new ExportService(folderService, cipherService, cryptoService); var auditService = new AuditService(cryptoFunctionService, apiService); - var environmentService = new EnvironmentService(apiService, storageService); - var eventService = new EventService(storageService, apiService, userService, cipherService); - var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, cryptoService); + var environmentService = new EnvironmentService(apiService, stateService); + var eventService = new EventService(apiService, stateService, organizationService, cipherService); + var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, + cryptoService); - Register("stateService", stateService); Register("tokenService", tokenService); Register("apiService", apiService); Register("appIdService", appIdService); - Register("userService", userService); + Register("organizationService", organizationService); Register("settingsService", settingsService); Register("cipherService", cipherService); Register("folderService", folderService); diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index 2a9fed346..e0e529bdb 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -79,9 +79,9 @@ namespace Bit.iOS.Autofill public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity) { InitAppIfNeeded(); - var storageService = ServiceContainer.Resolve("storageService"); - await storageService.SaveAsync(Bit.Core.Constants.PasswordRepromptAutofillKey, false); - await storageService.SaveAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey, false); + var stateService = ServiceContainer.Resolve("stateService"); + await stateService.SetPasswordRepromptAutofillAsync(false); + await stateService.SetPasswordVerifiedAutofillAsync(false); if (!await IsAuthed() || await IsLocked()) { var err = new NSError(new NSString("ASExtensionErrorDomain"), @@ -230,7 +230,7 @@ namespace Bit.iOS.Autofill return; } - var storageService = ServiceContainer.Resolve("storageService"); + var stateService = ServiceContainer.Resolve("stateService"); var decCipher = await cipher.DecryptAsync(); if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None) { @@ -238,13 +238,13 @@ namespace Bit.iOS.Autofill // already verified the password. if (!userInteraction) { - await storageService.SaveAsync(Bit.Core.Constants.PasswordRepromptAutofillKey, true); + await stateService.SetPasswordRepromptAutofillAsync(true); var err = new NSError(new NSString("ASExtensionErrorDomain"), Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null); ExtensionContext?.CancelRequest(err); return; } - else if (!await storageService.GetAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey)) + else if (!await stateService.GetPasswordVerifiedAutofillAsync()) { // Add a timeout to resolve keyboard not always showing up. await Task.Delay(250); @@ -259,11 +259,10 @@ namespace Bit.iOS.Autofill } } string totpCode = null; - var disableTotpCopy = await storageService.GetAsync(Bit.Core.Constants.DisableAutoTotpCopyKey); + var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync(); if (!disableTotpCopy.GetValueOrDefault(false)) { - var userService = ServiceContainer.Resolve("userService"); - var canAccessPremiumAsync = await userService.CanAccessPremiumAsync(); + var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync(); if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) && (canAccessPremiumAsync || cipher.OrganizationUseTotp)) { @@ -277,8 +276,8 @@ namespace Bit.iOS.Autofill private async void CheckLock(Action notLockedAction) { - var storageService = ServiceContainer.Resolve("storageService"); - if (await IsLocked() || await storageService.GetAsync(Bit.Core.Constants.PasswordRepromptAutofillKey)) + var stateService = ServiceContainer.Resolve("stateService"); + if (await IsLocked() || await stateService.GetPasswordRepromptAutofillAsync()) { PerformSegue("lockPasswordSegue", this); } @@ -296,8 +295,8 @@ namespace Bit.iOS.Autofill private Task IsAuthed() { - var userService = ServiceContainer.Resolve("userService"); - return userService.IsAuthenticatedAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + return stateService.IsAuthenticatedAsync(); } private void LogoutIfAuthed() @@ -306,7 +305,8 @@ namespace Bit.iOS.Autofill { if (await IsAuthed()) { - await AppHelpers.LogOutAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync()); var deviceActionService = ServiceContainer.Resolve("deviceActionService"); if (deviceActionService.SystemMajorVersion() >= 12) { @@ -337,7 +337,7 @@ namespace Bit.iOS.Autofill } iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); iOSCoreHelpers.AppearanceAdjustments(); _nfcDelegate = new Core.NFCReaderDelegate((success, message) => messagingService.Send("gotYubiKeyOTP", message)); @@ -356,7 +356,7 @@ namespace Bit.iOS.Autofill { var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(homePage); if (homePage.BindingContext is HomeViewModel vm) { @@ -379,7 +379,7 @@ namespace Bit.iOS.Autofill { var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { @@ -397,7 +397,7 @@ namespace Bit.iOS.Autofill { var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { @@ -415,7 +415,7 @@ namespace Bit.iOS.Autofill { var loginPage = new LoginPage(email); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { @@ -437,7 +437,7 @@ namespace Bit.iOS.Autofill { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -460,7 +460,7 @@ namespace Bit.iOS.Autofill { var twoFactorPage = new TwoFactorPage(authingWithSso); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { @@ -487,7 +487,7 @@ namespace Bit.iOS.Autofill { var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { @@ -506,7 +506,7 @@ namespace Bit.iOS.Autofill { var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { diff --git a/src/iOS.Autofill/Utilities/AutofillHelpers.cs b/src/iOS.Autofill/Utilities/AutofillHelpers.cs index fa1895968..2f274b913 100644 --- a/src/iOS.Autofill/Utilities/AutofillHelpers.cs +++ b/src/iOS.Autofill/Utilities/AutofillHelpers.cs @@ -41,12 +41,11 @@ namespace Bit.iOS.Autofill.Utilities if (!string.IsNullOrWhiteSpace(item.Username) && !string.IsNullOrWhiteSpace(item.Password)) { string totp = null; - var storageService = ServiceContainer.Resolve("storageService"); - var disableTotpCopy = await storageService.GetAsync(Bit.Core.Constants.DisableAutoTotpCopyKey); + var stateService = ServiceContainer.Resolve("stateService"); + var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync(); if (!disableTotpCopy.GetValueOrDefault(false)) { - var userService = ServiceContainer.Resolve("userService"); - var canAccessPremiumAsync = await userService.CanAccessPremiumAsync(); + var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync(); if (!string.IsNullOrWhiteSpace(item.Totp) && (canAccessPremiumAsync || item.CipherView.OrganizationUseTotp)) { @@ -118,4 +117,4 @@ namespace Bit.iOS.Autofill.Utilities } } } -} \ No newline at end of file +} diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index 2a238111e..c2f57fd41 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -23,13 +23,13 @@ namespace Bit.iOS.Core.Controllers private IVaultTimeoutService _vaultTimeoutService; private ICryptoService _cryptoService; private IDeviceActionService _deviceActionService; - private IUserService _userService; - private IStorageService _storageService; + private IStateService _stateService; private IStorageService _secureStorageService; private IPlatformUtilsService _platformUtilsService; private IBiometricService _biometricService; private IKeyConnectorService _keyConnectorService; - private Tuple _pinSet; + private bool _isPinProtected; + private bool _isPinProtectedWithKey; private bool _pinLock; private bool _biometricLock; private bool _biometricIntegrityValid = true; @@ -84,8 +84,7 @@ namespace Bit.iOS.Core.Controllers _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); _cryptoService = ServiceContainer.Resolve("cryptoService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); - _userService = ServiceContainer.Resolve("userService"); - _storageService = ServiceContainer.Resolve("storageService"); + _stateService = ServiceContainer.Resolve("stateService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _biometricService = ServiceContainer.Resolve("biometricService"); @@ -93,21 +92,22 @@ namespace Bit.iOS.Core.Controllers // We re-use the lock screen for autofill extension to verify master password // when trying to access protected items. - if (autofillExtension && await _storageService.GetAsync(Bit.Core.Constants.PasswordRepromptAutofillKey)) + if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) { _passwordReprompt = true; - _pinSet = Tuple.Create(false, false); + _isPinProtected = false; + _isPinProtectedWithKey = false; _pinLock = false; _biometricLock = false; } else { - _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); - _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; - _biometricLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult() && - _cryptoService.HasKeyAsync().GetAwaiter().GetResult(); - _biometricIntegrityValid = _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey).GetAwaiter() - .GetResult(); + (_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); + _pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) || + _isPinProtectedWithKey; + _biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && + await _cryptoService.HasKeyAsync(); + _biometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; } @@ -213,9 +213,9 @@ namespace Bit.iOS.Core.Controllers return; } - var email = await _userService.GetEmailAsync(); - var kdf = await _userService.GetKdfAsync(); - var kdfIterations = await _userService.GetKdfIterationsAsync(); + var email = await _stateService.GetEmailAsync(); + var kdf = await _stateService.GetKdfTypeAsync(); + var kdfIterations = await _stateService.GetKdfIterationsAsync(); var inputtedValue = MasterPasswordCell.TextField.Text; if (_pinLock) @@ -223,13 +223,13 @@ namespace Bit.iOS.Core.Controllers var failed = true; try { - if (_pinSet.Item1) + if (_isPinProtected) { var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), - _vaultTimeoutService.PinProtectedKey); + await _stateService.GetPinProtectedKeyAsync()); var encKey = await _cryptoService.GetEncKeyAsync(key); - var protectedPin = await _storageService.GetAsync(Bit.Core.Constants.ProtectedPin); + var protectedPin = await _stateService.GetProtectedPinAsync(); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); failed = decPin != inputtedValue; if (!failed) @@ -280,14 +280,14 @@ namespace Bit.iOS.Core.Controllers var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); if (passwordValid) { - if (_pinSet.Item1) + if (_isPinProtected) { - var protectedPin = await _storageService.GetAsync(Bit.Core.Constants.ProtectedPin); + var protectedPin = await _stateService.GetProtectedPinAsync(); var encKey = await _cryptoService.GetEncKeyAsync(key2); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); - _vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey); + await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey)); } await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key2, true); @@ -314,7 +314,7 @@ namespace Bit.iOS.Core.Controllers var success = await _platformUtilsService.AuthenticateBiometricAsync(null, _pinLock ? AppResources.PIN : AppResources.MasterPassword, () => MasterPasswordCell.TextField.BecomeFirstResponder()); - _vaultTimeoutService.BiometricLocked = !success; + _stateService.BiometricLocked = !success; if (success) { DoContinue(); @@ -325,7 +325,7 @@ namespace Bit.iOS.Core.Controllers { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -353,10 +353,10 @@ namespace Bit.iOS.Core.Controllers { if (masterPassword) { - await _storageService.SaveAsync(Bit.Core.Constants.PasswordVerifiedAutofillKey, true); + await _stateService.SetPasswordVerifiedAutofillAsync(true); } await EnableBiometricsIfNeeded(); - _vaultTimeoutService.BiometricLocked = false; + _stateService.BiometricLocked = false; MasterPasswordCell.TextField.ResignFirstResponder(); Success(); } @@ -385,7 +385,7 @@ namespace Bit.iOS.Core.Controllers private async Task LogOutAsync() { - await AppHelpers.LogOutAsync(); + await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync()); var authService = ServiceContainer.Resolve("authService"); authService.LogOut(() => { diff --git a/src/iOS.Core/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs b/src/iOS.Core/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs new file mode 100644 index 000000000..17450523c --- /dev/null +++ b/src/iOS.Core/Effects/ScrollViewContentInsetAdjustmentBehaviorEffect.cs @@ -0,0 +1,37 @@ +using Bit.iOS.Core.Effects; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportEffect(typeof(ScrollViewContentInsetAdjustmentBehaviorEffect), nameof(ScrollViewContentInsetAdjustmentBehaviorEffect))] +namespace Bit.iOS.Core.Effects +{ + public class ScrollViewContentInsetAdjustmentBehaviorEffect : PlatformEffect + { + protected override void OnAttached() + { + if (Element != null && Control is UIScrollView scrollView) + { + switch (App.Effects.ScrollViewContentInsetAdjustmentBehaviorEffect.GetContentInsetAdjustmentBehavior(Element)) + { + case App.Effects.ScrollContentInsetAdjustmentBehavior.Automatic: + scrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Automatic; + break; + case App.Effects.ScrollContentInsetAdjustmentBehavior.ScrollableAxes: + scrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.ScrollableAxes; + break; + case App.Effects.ScrollContentInsetAdjustmentBehavior.Never: + scrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Never; + break; + case App.Effects.ScrollContentInsetAdjustmentBehavior.Always: + scrollView.ContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.Always; + break; + } + } + } + + protected override void OnDetached() + { + } + } +} diff --git a/src/iOS.Core/Renderers/CustomNavigationRenderer.cs b/src/iOS.Core/Renderers/CustomNavigationRenderer.cs new file mode 100644 index 000000000..4242ca882 --- /dev/null +++ b/src/iOS.Core/Renderers/CustomNavigationRenderer.cs @@ -0,0 +1,87 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Bit.App.Controls; +using Bit.iOS.Core.Renderers; +using CoreFoundation; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))] +namespace Bit.iOS.Core.Renderers +{ + public class CustomNavigationRenderer : NavigationRenderer + { + public override void PushViewController(UIViewController viewController, bool animated) + { + base.PushViewController(viewController, animated); + + var currentPage = (Element as NavigationPage)?.CurrentPage; + if (currentPage == null) + { + return; + } + var toolbarItems = currentPage.ToolbarItems; + if (!toolbarItems.Any()) + { + return; + } + + // In order to get the correct index we need to do the same as XF and reverse the toolbar items list + // https://github.com/xamarin/Xamarin.Forms/blob/8f765bd87a2968bef9c86122d88c9c47be9196d2/Xamarin.Forms.Platform.iOS/Renderers/NavigationRenderer.cs#L1432 + toolbarItems = toolbarItems.Where(t => t.Order != ToolbarItemOrder.Secondary) + .Reverse() + .ToList(); + + var uiBarButtonItems = TopViewController.NavigationItem.RightBarButtonItems; + if (uiBarButtonItems == null) + { + return; + } + + foreach (ExtendedToolbarItem toolbarItem in toolbarItems.Where(t => t is ExtendedToolbarItem eti + && + eti.UseOriginalImage)) + { + var index = toolbarItems.IndexOf(toolbarItem); + if (index < 0 || index >= uiBarButtonItems.Length) + { + continue; + } + + // HACK: this is awful but I can't find another way to properly prevent memory leaks from + // subscribing on the PropertyChanged event; there are several private places where Xamarin Forms + // disposes objects that are not accessible from here so I think this should cover the (un)subscription + // but we need to remember to call the internal methods of ExtendedToolbarItem on the lifecycle of the Page + toolbarItem.OnAppearingAction = () => toolbarItem.PropertyChanged += ToolbarItem_PropertyChanged; + toolbarItem.OnDisappearingAction = () => toolbarItem.PropertyChanged -= ToolbarItem_PropertyChanged; + + // HACK: XF PimaryToolbarItem is sealed so we can't override it, and also it doesn't provide any + // direct way to replace it with our custom one (we can but we need to rewrite several parts of the NavigationRenderer) + // So I think this is the easiest soolution for now to set UIImageRenderingMode.AlwaysOriginal + // on the toolbar item image + void ToolbarItem_PropertyChanged(object s, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ExtendedToolbarItem.IconImageSource)) + { + var uiBarButtonItem = uiBarButtonItems[index]; + + DispatchQueue.MainQueue.DispatchAsync(() => + { + try + { + uiBarButtonItem.Image = uiBarButtonItem.Image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); + } + catch (ObjectDisposedException) + { + // Do nothing, we can't access the proper place to properly dispose this, so here + // we can just catch and ignore the exception. This should only happen when logging out a user. + } + }); + } + }; + } + } + } +} diff --git a/src/iOS.Core/Renderers/CustomTabbedRenderer.cs b/src/iOS.Core/Renderers/CustomTabbedRenderer.cs index c17d9d656..a8904ef6b 100644 --- a/src/iOS.Core/Renderers/CustomTabbedRenderer.cs +++ b/src/iOS.Core/Renderers/CustomTabbedRenderer.cs @@ -1,4 +1,6 @@ -using Bit.App.Abstractions; +using System; +using Bit.App.Abstractions; +using Bit.App.Pages; using Bit.Core.Abstractions; using Bit.Core.Utilities; using Bit.iOS.Core.Renderers; @@ -14,6 +16,7 @@ namespace Bit.iOS.Core.Renderers public class CustomTabbedRenderer : TabbedRenderer { private IBroadcasterService _broadcasterService; + private UITabBarItem _previousSelectedItem; public CustomTabbedRenderer() { @@ -39,6 +42,25 @@ namespace Bit.iOS.Core.Renderers UpdateTabBarAppearance(); } + public override void ViewDidAppear(bool animated) + { + base.ViewDidAppear(animated); + + if (SelectedIndex < TabBar.Items.Length) + { + _previousSelectedItem = TabBar.Items[SelectedIndex]; + } + } + + public override void ItemSelected(UITabBar tabbar, UITabBarItem item) + { + if (_previousSelectedItem == item && Element is TabsPage tabsPage) + { + tabsPage.OnPageReselected(); + } + _previousSelectedItem = item; + } + protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/iOS.Core/Services/ClipboardService.cs b/src/iOS.Core/Services/ClipboardService.cs index 6014da332..67f7743ac 100644 --- a/src/iOS.Core/Services/ClipboardService.cs +++ b/src/iOS.Core/Services/ClipboardService.cs @@ -9,11 +9,11 @@ namespace Bit.iOS.Core.Services { public class ClipboardService : IClipboardService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; - public ClipboardService(IStorageService storageService) + public ClipboardService(IStateService stateService) { - _storageService = storageService; + _stateService = stateService; } public async Task CopyTextAsync(string text, int expiresInMs = -1) @@ -21,7 +21,7 @@ namespace Bit.iOS.Core.Services int clearSeconds = -1; if (expiresInMs < 0) { - clearSeconds = await _storageService.GetAsync(Bit.Core.Constants.ClearClipboardKey) ?? -1; + clearSeconds = await _stateService.GetClearClipboardAsync() ?? -1; } else { diff --git a/src/iOS.Core/Services/DeviceActionService.cs b/src/iOS.Core/Services/DeviceActionService.cs index 76c46b066..39e59c9af 100644 --- a/src/iOS.Core/Services/DeviceActionService.cs +++ b/src/iOS.Core/Services/DeviceActionService.cs @@ -22,17 +22,17 @@ namespace Bit.iOS.Core.Services { public class DeviceActionService : IDeviceActionService { - private readonly IStorageService _storageService; + private readonly IStateService _stateService; private readonly IMessagingService _messagingService; private Toast _toast; private UIAlertController _progressAlert; private string _userAgent; public DeviceActionService( - IStorageService storageService, + IStateService stateService, IMessagingService messagingService) { - _storageService = storageService; + _stateService = stateService; _messagingService = messagingService; } @@ -152,7 +152,7 @@ namespace Bit.iOS.Core.Services NSFileManager.DefaultManager.Remove(item, out NSError itemError); } } - await _storageService.SaveAsync(Bit.Core.Constants.LastFileCacheClearKey, DateTime.UtcNow); + await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow); } public Task SelectFileAsync() diff --git a/src/iOS.Core/Utilities/ASHelpers.cs b/src/iOS.Core/Utilities/ASHelpers.cs index 5e81438fb..74c7665a9 100644 --- a/src/iOS.Core/Utilities/ASHelpers.cs +++ b/src/iOS.Core/Utilities/ASHelpers.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using AuthenticationServices; using Bit.Core.Abstractions; +using Bit.Core.Enums; using Bit.Core.Models.View; using Bit.Core.Utilities; @@ -15,8 +16,9 @@ namespace Bit.iOS.Core.Utilities if (await AutofillEnabled()) { var storageService = ServiceContainer.Resolve("storageService"); - var timeoutAction = await storageService.GetAsync(Bit.Core.Constants.VaultTimeoutActionKey); - if (timeoutAction == "logOut") + var stateService = ServiceContainer.Resolve("stateService"); + var timeoutAction = await stateService.GetVaultTimeoutActionAsync(); + if (timeoutAction == VaultTimeoutAction.Logout) { return; } @@ -47,9 +49,9 @@ namespace Bit.iOS.Core.Utilities public static async Task IdentitiesCanIncremental() { - var storageService = ServiceContainer.Resolve("storageService"); - var timeoutAction = await storageService.GetAsync(Bit.Core.Constants.VaultTimeoutActionKey); - if (timeoutAction == "logOut") + var stateService = ServiceContainer.Resolve("stateService"); + var timeoutAction = await stateService.GetVaultTimeoutActionAsync(); + if (timeoutAction == VaultTimeoutAction.Logout) { return false; } diff --git a/src/iOS.Core/Utilities/AppCenterHelper.cs b/src/iOS.Core/Utilities/AppCenterHelper.cs index 50b64d154..ff19b62a5 100644 --- a/src/iOS.Core/Utilities/AppCenterHelper.cs +++ b/src/iOS.Core/Utilities/AppCenterHelper.cs @@ -11,22 +11,22 @@ namespace Bit.iOS.Core.Utilities private const string AppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3"; private readonly IAppIdService _appIdService; - private readonly IUserService _userService; + private readonly IStateService _stateService; private string _userId; private string _appId; public AppCenterHelper( IAppIdService appIdService, - IUserService userService) + IStateService stateService) { _appIdService = appIdService; - _userService = userService; + _stateService = stateService; } public async Task InitAsync() { - _userId = await _userService.GetUserIdAsync(); + _userId = await _stateService.GetActiveUserIdAsync(); _appId = await _appIdService.GetAppIdAsync(); AppCenter.Start(AppSecret, typeof(Crashes)); diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index 1bac23f65..be8034106 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -29,7 +29,7 @@ namespace Bit.iOS.Core.Utilities { var appCenterHelper = new AppCenterHelper( ServiceContainer.Resolve("appIdService"), - ServiceContainer.Resolve("userService")); + ServiceContainer.Resolve("stateService")); var appCenterTask = appCenterHelper.InitAsync(); } @@ -52,13 +52,16 @@ namespace Bit.iOS.Core.Utilities () => ServiceContainer.Resolve("appIdService").GetAppIdAsync()); var cryptoPrimitiveService = new CryptoPrimitiveService(); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); - var deviceActionService = new DeviceActionService(mobileStorageService, messagingService); - var clipboardService = new ClipboardService(mobileStorageService); + var stateService = new StateService(mobileStorageService, secureStorageService); + var stateMigrationService = + new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService); + var deviceActionService = new DeviceActionService(stateService, messagingService); + var clipboardService = new ClipboardService(stateService); var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, broadcasterService); var biometricService = new BiometricService(mobileStorageService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); - var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService); + var cryptoService = new CryptoService(stateService, cryptoFunctionService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); ServiceContainer.Register("broadcasterService", broadcasterService); @@ -68,6 +71,8 @@ namespace Bit.iOS.Core.Utilities ServiceContainer.Register("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register("storageService", mobileStorageService); ServiceContainer.Register("secureStorageService", secureStorageService); + ServiceContainer.Register("stateService", stateService); + ServiceContainer.Register("stateMigrationService", stateMigrationService); ServiceContainer.Register("deviceActionService", deviceActionService); ServiceContainer.Register("clipboardService", clipboardService); ServiceContainer.Register("platformUtilsService", platformUtilsService); @@ -89,7 +94,7 @@ namespace Bit.iOS.Core.Utilities public static void AppearanceAdjustments() { - ThemeHelpers.SetAppearance(ThemeManager.GetTheme(false), ThemeManager.OsDarkModeEnabled()); + ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled()); UIApplication.SharedApplication.StatusBarHidden = false; UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent; } @@ -144,10 +149,6 @@ namespace Bit.iOS.Core.Utilities private static async Task BootstrapAsync(Func postBootstrapFunc = null) { - var disableFavicon = await ServiceContainer.Resolve("storageService").GetAsync( - Bit.Core.Constants.DisableFaviconKey); - await ServiceContainer.Resolve("stateService").SaveAsync( - Bit.Core.Constants.DisableFaviconKey, disableFavicon); await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync(); // TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged diff --git a/src/iOS.Core/Views/ExtensionTableSource.cs b/src/iOS.Core/Views/ExtensionTableSource.cs index 9aa5e1b4b..4bd856b1e 100644 --- a/src/iOS.Core/Views/ExtensionTableSource.cs +++ b/src/iOS.Core/Views/ExtensionTableSource.cs @@ -23,7 +23,7 @@ namespace Bit.iOS.Core.Views private IEnumerable _allItems = new List(); protected ICipherService _cipherService; protected ITotpService _totpService; - protected IUserService _userService; + protected IStateService _stateService; protected ISearchService _searchService; private AppExtensionContext _context; private UIViewController _controller; @@ -32,7 +32,7 @@ namespace Bit.iOS.Core.Views { _cipherService = ServiceContainer.Resolve("cipherService"); _totpService = ServiceContainer.Resolve("totpService"); - _userService = ServiceContainer.Resolve("userService"); + _stateService = ServiceContainer.Resolve("stateService"); _searchService = ServiceContainer.Resolve("searchService"); _context = context; _controller = controller; @@ -135,7 +135,7 @@ namespace Bit.iOS.Core.Views public async Task GetTotpAsync(CipherViewModel item) { string totp = null; - var accessPremium = await _userService.CanAccessPremiumAsync(); + var accessPremium = await _stateService.CanAccessPremiumAsync(); if (accessPremium || (item?.CipherView.OrganizationUseTotp ?? false)) { if (item != null && !string.IsNullOrWhiteSpace(item.Totp)) diff --git a/src/iOS.Core/iOS.Core.csproj b/src/iOS.Core/iOS.Core.csproj index 34cd2477e..823b2bacd 100644 --- a/src/iOS.Core/iOS.Core.csproj +++ b/src/iOS.Core/iOS.Core.csproj @@ -156,6 +156,7 @@ + @@ -193,6 +194,7 @@ + diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index 44acc0084..a7b6cf484 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -415,7 +415,7 @@ namespace Bit.iOS.Extension } iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); iOSCoreHelpers.AppearanceAdjustments(); _nfcDelegate = new NFCReaderDelegate((success, message) => messagingService.Send("gotYubiKeyOTP", message)); @@ -430,8 +430,8 @@ namespace Bit.iOS.Extension private Task IsAuthed() { - var userService = ServiceContainer.Resolve("userService"); - return userService.IsAuthenticatedAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + return stateService.IsAuthenticatedAsync(); } private void LogoutIfAuthed() @@ -440,7 +440,8 @@ namespace Bit.iOS.Extension { if (await IsAuthed()) { - await AppHelpers.LogOutAsync(); + var stateService = ServiceContainer.Resolve("stateService"); + await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync()); var deviceActionService = ServiceContainer.Resolve("deviceActionService"); if (deviceActionService.SystemMajorVersion() >= 12) { @@ -454,7 +455,7 @@ namespace Bit.iOS.Extension { var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(homePage); if (homePage.BindingContext is HomeViewModel vm) { @@ -477,7 +478,7 @@ namespace Bit.iOS.Extension { var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { @@ -495,7 +496,7 @@ namespace Bit.iOS.Extension { var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { @@ -513,7 +514,7 @@ namespace Bit.iOS.Extension { var loginPage = new LoginPage(email); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { @@ -535,7 +536,7 @@ namespace Bit.iOS.Extension { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -558,7 +559,7 @@ namespace Bit.iOS.Extension { var twoFactorPage = new TwoFactorPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { @@ -585,7 +586,7 @@ namespace Bit.iOS.Extension { var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { @@ -604,7 +605,7 @@ namespace Bit.iOS.Extension { var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { diff --git a/src/iOS.Extension/LoginListViewController.cs b/src/iOS.Extension/LoginListViewController.cs index 2dd2f7455..ea7917c76 100644 --- a/src/iOS.Extension/LoginListViewController.cs +++ b/src/iOS.Extension/LoginListViewController.cs @@ -9,8 +9,6 @@ using MobileCoreServices; using Bit.iOS.Core.Controllers; using Bit.App.Resources; using Bit.iOS.Core.Views; -using Bit.Core.Utilities; -using Bit.Core.Abstractions; namespace Bit.iOS.Extension { @@ -127,9 +125,7 @@ namespace Bit.iOS.Extension if (_controller.CanAutoFill() && !string.IsNullOrWhiteSpace(item.Password)) { string totp = null; - var storageService = ServiceContainer.Resolve("storageService"); - var disableTotpCopy = storageService.GetAsync( - Bit.Core.Constants.DisableAutoTotpCopyKey).GetAwaiter().GetResult(); + var disableTotpCopy = _stateService.GetDisableAutoTotpCopyAsync().GetAwaiter().GetResult(); if (!disableTotpCopy.GetValueOrDefault(false)) { totp = GetTotpAsync(item).GetAwaiter().GetResult(); diff --git a/src/iOS.ShareExtension/LoadingViewController.cs b/src/iOS.ShareExtension/LoadingViewController.cs index 651fe21ec..6a3304f26 100644 --- a/src/iOS.ShareExtension/LoadingViewController.cs +++ b/src/iOS.ShareExtension/LoadingViewController.cs @@ -31,7 +31,7 @@ namespace Bit.iOS.ShareExtension private NFCNdefReaderSession _nfcSession = null; private Core.NFCReaderDelegate _nfcDelegate = null; - readonly LazyResolve _userService = new LazyResolve("userService"); + readonly LazyResolve _stateService = new LazyResolve("stateervice"); readonly LazyResolve _vaultTimeoutService = new LazyResolve("vaultTimeoutService"); readonly LazyResolve _deviceActionService = new LazyResolve("deviceActionService"); readonly LazyResolve _eventService = new LazyResolve("eventService"); @@ -148,7 +148,7 @@ namespace Bit.iOS.ShareExtension }; var app = new App.App(appOptions); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(sendAddEditPage); var navigationPage = new NavigationPage(sendAddEditPage); @@ -224,7 +224,7 @@ namespace Bit.iOS.ShareExtension iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); iOSCoreHelpers.AppearanceAdjustments(); _nfcDelegate = new NFCReaderDelegate((success, message) => @@ -239,7 +239,7 @@ namespace Bit.iOS.ShareExtension private Task IsAuthed() { - return _userService.Value.IsAuthenticatedAsync(); + return _stateService.Value.IsAuthenticatedAsync(); } private void LogoutIfAuthed() @@ -248,7 +248,7 @@ namespace Bit.iOS.ShareExtension { if (await IsAuthed()) { - await AppHelpers.LogOutAsync(); + await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync()); if (_deviceActionService.Value.SystemMajorVersion() >= 12) { await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); @@ -261,7 +261,7 @@ namespace Bit.iOS.ShareExtension { var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(homePage); if (homePage.BindingContext is HomeViewModel vm) { @@ -284,7 +284,7 @@ namespace Bit.iOS.ShareExtension { var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { @@ -302,7 +302,7 @@ namespace Bit.iOS.ShareExtension { var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { @@ -320,7 +320,7 @@ namespace Bit.iOS.ShareExtension { var loginPage = new LoginPage(email); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { @@ -342,7 +342,7 @@ namespace Bit.iOS.ShareExtension { var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { @@ -365,7 +365,7 @@ namespace Bit.iOS.ShareExtension { var twoFactorPage = new TwoFactorPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { @@ -392,7 +392,7 @@ namespace Bit.iOS.ShareExtension { var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { @@ -411,7 +411,7 @@ namespace Bit.iOS.ShareExtension { var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); - ThemeManager.SetTheme(false, app.Resources); + ThemeManager.SetTheme(app.Resources); ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 48c1bdd64..a426a6a3b 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -8,6 +8,7 @@ using Bit.App.Services; using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; +using Bit.Core.Enums; using Bit.Core.Services; using Bit.Core.Utilities; using Bit.iOS.Core.Utilities; @@ -35,7 +36,7 @@ namespace Bit.iOS private IMessagingService _messagingService; private IBroadcasterService _broadcasterService; private IStorageService _storageService; - private IVaultTimeoutService _vaultTimeoutService; + private IStateService _stateService; private IEventService _eventService; public override bool FinishedLaunching(UIApplication app, NSDictionary options) @@ -47,7 +48,7 @@ namespace Bit.iOS _messagingService = ServiceContainer.Resolve("messagingService"); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _storageService = ServiceContainer.Resolve("storageService"); - _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); + _stateService = ServiceContainer.Resolve("stateService"); _eventService = ServiceContainer.Resolve("eventService"); LoadApplication(new App.App(null)); @@ -88,11 +89,6 @@ namespace Bit.iOS { Device.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data)); } - else if (message.Command == "showStatusBar") - { - Device.BeginInvokeOnMainThread(() => - UIApplication.SharedApplication.SetStatusBarHidden(!(bool)message.Data, false)); - } else if (message.Command == "syncCompleted") { if (message.Data is Dictionary data && data.ContainsKey("successfully")) @@ -146,11 +142,16 @@ namespace Bit.iOS await ASHelpers.ReplaceAllIdentities(); } } - else if (message.Command == "loggedOut") + else if (message.Command == "logout") { if (_deviceActionService.SystemMajorVersion() >= 12) { - await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); + var extras = message.Data as Tuple; + var userId = extras?.Item1; + var userInitiated = extras?.Item2; + var expired = extras?.Item3; + // TODO make specific to userId + // await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); } } else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher") @@ -160,10 +161,12 @@ namespace Bit.iOS } else if (message.Command == "vaultTimeoutActionChanged") { - var timeoutAction = await _storageService.GetAsync(Constants.VaultTimeoutActionKey); - if (timeoutAction == "logOut") + var timeoutAction = await _stateService.GetVaultTimeoutActionAsync(); + if (timeoutAction == VaultTimeoutAction.Logout) { - await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); + var userId = await _stateService.GetActiveUserIdAsync(); + // TODO make specific to userId + // await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); } else { @@ -195,13 +198,12 @@ namespace Bit.iOS UIApplication.SharedApplication.KeyWindow.AddSubview(view); UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view); UIApplication.SharedApplication.KeyWindow.EndEditing(true); - UIApplication.SharedApplication.SetStatusBarHidden(true, false); base.OnResignActivation(uiApplication); } public override void DidEnterBackground(UIApplication uiApplication) { - _storageService?.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime()); + _stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); _messagingService?.Send("slept"); base.DidEnterBackground(uiApplication); } @@ -214,7 +216,6 @@ namespace Bit.iOS if (view != null) { view.RemoveFromSuperview(); - UIApplication.SharedApplication.SetStatusBarHidden(false, false); } ThemeManager.UpdateThemeOnPagesAsync(); diff --git a/src/iOS/LaunchScreen.storyboard b/src/iOS/LaunchScreen.storyboard index 393e62a39..6cec090bc 100644 --- a/src/iOS/LaunchScreen.storyboard +++ b/src/iOS/LaunchScreen.storyboard @@ -88,4 +88,4 @@ - + \ No newline at end of file diff --git a/src/iOS/Resources/cog.png b/src/iOS/Resources/cog_environment.png similarity index 100% rename from src/iOS/Resources/cog.png rename to src/iOS/Resources/cog_environment.png diff --git a/src/iOS/Resources/cog@2x.png b/src/iOS/Resources/cog_environment@2x.png similarity index 100% rename from src/iOS/Resources/cog@2x.png rename to src/iOS/Resources/cog_environment@2x.png diff --git a/src/iOS/Resources/cog@3x.png b/src/iOS/Resources/cog_environment@3x.png similarity index 100% rename from src/iOS/Resources/cog@3x.png rename to src/iOS/Resources/cog_environment@3x.png diff --git a/src/iOS/Resources/cog_settings.png b/src/iOS/Resources/cog_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..95d6271b8384e603affd5267b726f1a6875d989d GIT binary patch literal 486 zcmV@P)X1^@s6$@ldn00006VoOIv0RI60 z0RN!9r;`8x0h>ugK~zYIwboBdRY4fX@sEZ{K`t^}r1%!x1c^j{$*`rBxa;ls0#eX| z+O;eQS8k# zI6@O&Fxx|}N*cl60@@jSxKt(CU`6;8E=Imxz?C}T!x(6Lew^t42t!o{P^M9A2-CTP zC)id#-JP+CB{Z;)SK_l(syCUS+*r>A->Dhgin$zkqfhXUDey=}k|tI$jY(X?n$x=P z7G2kV5I+;(g@|_{F@WDPDq&XGrDX}8C%i8rz5~psz%4CClH5x8Zby6%$~wmmnQxKy z9|`#PNc(OT_;{p!G6A26v|mp6PBf?YHGdZI{lm=^_zZqU5*!J;Ikwy9b>HpVDfnm? zbNc|EVn&$Gt60HdhWC7G0Gk;}dWjDyFg)xN{B8~$-qs9$oeL(c<@>_;70W^m=YY0^ z|Nl<${!Yc~RZHz>@j1LqfY)%g=s!?+YSaA$e5OuVr^bX^Y8m*CaEa`z?g{1CbXxbH caJ_1xW2Y`ZLqtnBl>h($07*qoM6N<$g2+qXvj6}9 literal 0 HcmV?d00001 diff --git a/src/iOS/Resources/cog_settings@2x.png b/src/iOS/Resources/cog_settings@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8d884844f08c485b529fb3b889b366a348d0f846 GIT binary patch literal 931 zcmV;U16=%xP)%jv700006VoOIv0RI60 z0RN!9r;`8x16WB!K~!jg)tWnK6hR!uf0xT~N+c$QQG+j3@KuOS5PTIPXatF(b{0_) z1NJJ2f}(<;LC`9Vg@S@aEs`L;+1S=*uZ$KokCfbFmpH4a2N74x$&_X%BLbQOKwLlWcr;p?IBlqV)>8M1q3$?T0Iv-VbYi|8 z6$f4z>V846)~^Cu4NTNo>TWmC>;#rr>P|u&xTay^JNBE937}U)vk8aGU!8)dRY`PUzye?GpyrrHms)4&2CZwjiod%lMQMHK(DuFi!CcHV<^qVb3Eq+XNp8``1b;seN zp#hI)40SJ9>K--J{f8Lp+qE%~L2qFb1C9x3b{QoegP8&*JT?RK1N0Unw`G7~RaMMQ$@=5H&UH6lRm~9J!}oOt#pB_OK@cK}@(#B76*Z z5`i!}yde?X!)nyG6LzRVm_qFZisEVnf)&D((!oJ3Fn&ZJTt0TAj>(;%j{L{OVZZJh zTm|<~1KdX)3S@vkr~+ma>g_mydIFNcx={OhWvE)<(HQ-7*3T-Jp(n!oSxPcT+i^w> z+C0q${0pB7)akL`&tf70+ZuhJOLMNbu{td8e=Oo62V95mv%J4o#6?I*0zWYyYUR9lu2*;@$s-{?YX5B7(~bP3i1cM&T9rap)OFZTg zN!EJ7LhLjxGHN#!Su_4r!?sT|Y&$VoGrrZZ?H0qf6O%RL&ogYh!?5jK(*+D*^TS#J zO2B2lk_p8Gr1#DT&WN~n2JkdujFsut7yupxPEpA+Lo5QeGI9d+Bd~_r@9SaZEXS^-CXPvXeH$`{9hdY$3KW5lfscTnkom(DWXLXx^%% zwG6xnO!c6-AxqI2>0IwY>k0*(MPRW9t+CjQTQVj5^qy(Be?r%52+^t}`@!-4pMfJ9 zLLxYfyVgA#LbQx`ZAT@UnEd8=zsd+EE47mY9-rmolNCu;jGyT7S(zx`(=tBk^73%b z)DRJqnU43DHAH9`?^*&q7nrOeB!ZKGS&nVY&=8`*h3KpRc6-pOQT6sGaFz$H4H*aA zk6yL!YaTRfm82ZPW-%|4Nx=IFgvN;>WTVB33((&fc@ED5lvkA#sKz!!&F>g#e#B67 z4a`3i5-k@&198 z_}O!HU51FH(?b%~CONwGScW;bOJ8SY)1;@)PXf(%*r`g&jiDO(HO+d{`nu12Qe zij-oTCHjCl&Mu}zt3W7^ai4;Jsp-JJ5;u#n562xd*HVSQked0$j_u@)9z2z1pNR3u z*`(0D&{qWcL2blj;0>Zh??K*I^D~nU$Pz>x(?cvtL~;Z00kuoVL&y=$ zRn(pqYzJPz?+RiyjsW*idqQvua3^x2@c3)Xz$d_RM{m5)g1nP#9?_@uI*Ipb(TJnK z6GYdv&p{58EkG8(nwp!jHN6kG)LS+=V<&dTKjkrxNU~BpS><2cr`g3dm6zZR5B2Od_vzqicFU@>-QqVz3S!(~luD=1~%` z202rj*7W^Io#!Yin2G!!r(MWV$tbfAAnPh?k!rHc@-Gxz#S|#|Zyf*t002ovPDHLk FV1j}^xGw+z literal 0 HcmV?d00001 diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj index d2caf681a..1e901529b 100644 --- a/src/iOS/iOS.csproj +++ b/src/iOS/iOS.csproj @@ -143,6 +143,7 @@ + false @@ -155,19 +156,21 @@ - - - - - - + + + + + + + + - - + + diff --git a/test/Core.Test/Services/SendServiceTests.cs b/test/Core.Test/Services/SendServiceTests.cs index fa2e3f94b..badf2c742 100644 --- a/test/Core.Test/Services/SendServiceTests.cs +++ b/test/Core.Test/Services/SendServiceTests.cs @@ -28,20 +28,17 @@ namespace Bit.Core.Test.Services { public class SendServiceTests { - private string GetSendKey(string userId) => SendService.GetSendKey(userId); - [Theory] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })] public async Task ReplaceAsync_Success(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var actualSendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); await sutProvider.Sut.ReplaceAsync(actualSendDataDict); - await sutProvider.GetDependency() - .Received(1).SaveAsync(GetSendKey(userId), actualSendDataDict); + await sutProvider.GetDependency().SetEncryptedSendsAsync(actualSendDataDict); } [Theory] @@ -53,9 +50,8 @@ namespace Bit.Core.Test.Services public async Task DeleteAsync_Success(int numberToDelete, SutProvider sutProvider, string userId, IEnumerable sendDatas) { var actualSendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency() - .GetAsync>(GetSendKey(userId)).Returns(actualSendDataDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(actualSendDataDict); var idsToDelete = actualSendDataDict.Take(numberToDelete).Select(kvp => kvp.Key).ToArray(); var expectedSends = actualSendDataDict.Skip(numberToDelete).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); @@ -63,9 +59,8 @@ namespace Bit.Core.Test.Services await sutProvider.Sut.DeleteAsync(idsToDelete); - await sutProvider.GetDependency().Received(1) - .SaveAsync(GetSendKey(userId), - Arg.Is>(s => TestHelper.AssertEqualExpectedPredicate(expectedSends)(s))); + await sutProvider.GetDependency().SetEncryptedSendsAsync( + Arg.Is>(s => TestHelper.AssertEqualExpectedPredicate(expectedSends)(s))); } [Theory, SutAutoData] @@ -73,7 +68,7 @@ namespace Bit.Core.Test.Services { await sutProvider.Sut.ClearAsync(userId); - await sutProvider.GetDependency().Received(1).RemoveAsync(GetSendKey(userId)); + await sutProvider.GetDependency().SetEncryptedSendsAsync(null, userId); } [Theory] @@ -84,16 +79,15 @@ namespace Bit.Core.Test.Services var initialSendDatas = sendDatas.ToDictionary(d => d.Id, d => d); var idToDelete = initialSendDatas.First().Key; var expectedSends = initialSendDatas.Skip(1).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency() - .GetAsync>(Arg.Any()).Returns(initialSendDatas); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency() + .GetEncryptedSendsAsync(Arg.Any()).Returns(initialSendDatas); await sutProvider.Sut.DeleteWithServerAsync(idToDelete); await sutProvider.GetDependency().Received(1).DeleteSendAsync(idToDelete); - await sutProvider.GetDependency().Received(1) - .SaveAsync(GetSendKey(userId), - Arg.Is>(s => TestHelper.AssertEqualExpectedPredicate(expectedSends)(s))); + await sutProvider.GetDependency().SetEncryptedSendsAsync( + Arg.Is>(s => TestHelper.AssertEqualExpectedPredicate(expectedSends)(s))); } [Theory] @@ -102,8 +96,8 @@ namespace Bit.Core.Test.Services public async Task GetAsync_Success(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(sendDataDict); foreach (var dataKvp in sendDataDict) { @@ -114,11 +108,11 @@ namespace Bit.Core.Test.Services } [Theory, SutAutoData] - public async Task GetAsync_NonExistringId_ReturnsNull(SutProvider sutProvider, string userId, IEnumerable sendDatas) + public async Task GetAsync_NonExistingId_ReturnsNull(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(sendDataDict); var actual = await sutProvider.Sut.GetAsync(Guid.NewGuid().ToString()); @@ -131,8 +125,8 @@ namespace Bit.Core.Test.Services public async Task GetAllAsync_Success(SutProvider sutProvider, string userId, IEnumerable sendDatas) { var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(sendDataDict); var allExpected = sendDataDict.Select(kvp => new Send(kvp.Value)); var allActual = await sutProvider.Sut.GetAllAsync(); @@ -154,8 +148,8 @@ namespace Bit.Core.Test.Services sutProvider.GetDependency().HasKeyAsync().Returns(true); ServiceContainer.Register("cryptoService", sutProvider.GetDependency()); sutProvider.GetDependency().StringComparer.Returns(StringComparer.CurrentCulture); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(sendDataDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(sendDataDict); var actual = await sutProvider.Sut.GetAllDecryptedAsync(); @@ -175,7 +169,7 @@ namespace Bit.Core.Test.Services public async Task SaveWithServerAsync_NewTextSend_Success(SutProvider sutProvider, string userId, SendResponse response, Send send) { send.Id = null; - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().PostSendAsync(Arg.Any()).Returns(response); var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content")); @@ -208,7 +202,7 @@ namespace Bit.Core.Test.Services { send.Id = null; response.FileUploadType = FileUploadType.Azure; - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().PostFileTypeSendAsync(Arg.Any()).Returns(response); var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content")); @@ -231,7 +225,7 @@ namespace Bit.Core.Test.Services public async Task SaveWithServerAsync_NewFileSend_LegacyFallback_Success(SutProvider sutProvider, string userId, Send send, SendResponse response) { send.Id = null; - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); var error = new ErrorResponse(null, System.Net.HttpStatusCode.NotFound); sutProvider.GetDependency().PostFileTypeSendAsync(Arg.Any()).Throws(new ApiException(error)); sutProvider.GetDependency().PostSendFileAsync(Arg.Any()).Returns(response); @@ -248,7 +242,7 @@ namespace Bit.Core.Test.Services [InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })] public async Task SaveWithServerAsync_PutSend_Success(SutProvider sutProvider, string userId, SendResponse response, Send send) { - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency().PutSendAsync(send.Id, Arg.Any()).Returns(response); await sutProvider.Sut.SaveWithServerAsync(send, null); @@ -272,7 +266,7 @@ namespace Bit.Core.Test.Services await sutProvider.Sut.RemovePasswordWithServerAsync(sendId); await sutProvider.GetDependency().Received(1).PutSendRemovePasswordAsync(sendId); - await sutProvider.GetDependency().ReceivedWithAnyArgs(1).SaveAsync>(default, default); + await sutProvider.GetDependency().SetEncryptedSendsAsync(default, default); } [Theory] @@ -281,8 +275,8 @@ namespace Bit.Core.Test.Services public async Task UpsertAsync_Update_Success(SutProvider sutProvider, string userId, IEnumerable initialSends) { var initialSendDict = initialSends.ToDictionary(s => s.Id, s => s); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(initialSendDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(initialSendDict); var updatedSends = CoreHelpers.Clone(initialSendDict); foreach (var kvp in updatedSends) @@ -302,7 +296,8 @@ namespace Bit.Core.Test.Services } return true; }; - await sutProvider.GetDependency().Received(1).SaveAsync(GetSendKey(userId), Arg.Is>(d => matchSendsPredicate(d))); + await sutProvider.GetDependency().SetEncryptedSendsAsync( + Arg.Is>(d => matchSendsPredicate(d))); } [Theory] @@ -311,8 +306,8 @@ namespace Bit.Core.Test.Services public async Task UpsertAsync_NewSends_Success(SutProvider sutProvider, string userId, IEnumerable initialSends, IEnumerable newSends) { var initialSendDict = initialSends.ToDictionary(s => s.Id, s => s); - sutProvider.GetDependency().GetUserIdAsync().Returns(userId); - sutProvider.GetDependency().GetAsync>(GetSendKey(userId)).Returns(initialSendDict); + sutProvider.GetDependency().GetActiveUserIdAsync().Returns(userId); + sutProvider.GetDependency().GetEncryptedSendsAsync().Returns(initialSendDict); var expectedDict = CoreHelpers.Clone(initialSendDict).Concat(newSends.Select(s => new KeyValuePair(s.Id, s))); @@ -328,7 +323,8 @@ namespace Bit.Core.Test.Services } return true; }; - await sutProvider.GetDependency().Received(1).SaveAsync(GetSendKey(userId), Arg.Is>(d => matchSendsPredicate(d))); + await sutProvider.GetDependency().SetEncryptedSendsAsync( + Arg.Is>(d => matchSendsPredicate(d))); } [Theory] From 9201da85153640a2e4b390b6656ab727b466cad9 Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Wed, 23 Feb 2022 15:30:49 -0500 Subject: [PATCH 021/100] take environment into account when checking for existing account (#1808) --- src/App/Pages/Accounts/LoginPageViewModel.cs | 23 ++++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/App/Pages/Accounts/LoginPageViewModel.cs b/src/App/Pages/Accounts/LoginPageViewModel.cs index df2641b64..7514e9047 100644 --- a/src/App/Pages/Accounts/LoginPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginPageViewModel.cs @@ -141,15 +141,12 @@ namespace Bit.App.Pages var userId = await _stateService.GetUserIdAsync(Email); if (!string.IsNullOrWhiteSpace(userId)) { - var switchToAccount = await _platformUtilsService.ShowDialogAsync( - AppResources.SwitchToAlreadyAddedAccountConfirmation, - AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel); - if (switchToAccount) + var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId); + if (userEnvUrls?.Base == _environmentService.BaseUrl) { - await _stateService.SetActiveUserAsync(userId); - _messagingService.Send("switchedAccount"); + await PromptToSwitchToExistingAccountAsync(userId); + return; } - return; } } @@ -229,5 +226,17 @@ namespace Bit.App.Pages #endif } } + + private async Task PromptToSwitchToExistingAccountAsync(string userId) + { + var switchToAccount = await _platformUtilsService.ShowDialogAsync( + AppResources.SwitchToAlreadyAddedAccountConfirmation, + AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel); + if (switchToAccount) + { + await _stateService.SetActiveUserAsync(userId); + _messagingService.Send("switchedAccount"); + } + } } } From c74ed668b57b918a74081c4d9de6fcb3266ab488 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Thu, 24 Feb 2022 10:27:08 -0300 Subject: [PATCH 022/100] Changed link on Settings "Change Master Password" and "Two Step Login" to go to the web vault settings. Also refactored a bit to reuse the urls (#1809) --- src/App/Pages/Accounts/LockPageViewModel.cs | 2 +- src/App/Pages/CaptchaProtectedViewModel.cs | 12 +++++----- .../SettingsPage/SettingsPageViewModel.cs | 11 +++------- src/App/Utilities/AppHelpers.cs | 7 +----- src/Core/Abstractions/IEnvironmentService.cs | 3 ++- src/Core/Services/EnvironmentService.cs | 22 ++++++++++++++----- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index 6aa5d2b28..05607ccbb 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -156,7 +156,7 @@ namespace Bit.App.Pages #endif return; } - var webVault = _environmentService.GetWebVaultUrl(); + var webVault = _environmentService.GetWebVaultUrl(true); if (string.IsNullOrWhiteSpace(webVault)) { webVault = "https://bitwarden.com"; diff --git a/src/App/Pages/CaptchaProtectedViewModel.cs b/src/App/Pages/CaptchaProtectedViewModel.cs index 64693c5eb..d5d019dff 100644 --- a/src/App/Pages/CaptchaProtectedViewModel.cs +++ b/src/App/Pages/CaptchaProtectedViewModel.cs @@ -27,13 +27,11 @@ namespace Bit.App.Pages captchaRequiredText = AppResources.CaptchaRequired, }); - var url = environmentService.GetWebVaultUrl(); - if (url == null) - { - url = "https://vault.bitwarden.com"; - } - url += "/captcha-mobile-connector.html?" + "data=" + data + - "&parent=" + Uri.EscapeDataString(callbackUri) + "&v=1"; + var url = environmentService.GetWebVaultUrl() + + "/captcha-mobile-connector.html?" + + "data=" + data + + "&parent=" + Uri.EscapeDataString(callbackUri) + + "&v=1"; WebAuthenticatorResult authResult = null; bool cancelled = false; diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 87afeb2a0..b4f689af9 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -190,12 +190,7 @@ namespace Bit.App.Pages public void WebVault() { - var url = _environmentService.GetWebVaultUrl(); - if (url == null) - { - url = "https://vault.bitwarden.com"; - } - _platformUtilsService.LaunchUri(url); + _platformUtilsService.LaunchUri(_environmentService.GetWebVaultUrl()); } public async Task ShareAsync() @@ -214,7 +209,7 @@ namespace Bit.App.Pages AppResources.TwoStepLogin, AppResources.Yes, AppResources.Cancel); if (confirmed) { - _platformUtilsService.LaunchUri("https://bitwarden.com/help/setup-two-step-login/"); + _platformUtilsService.LaunchUri($"{_environmentService.GetWebVaultUrl()}/#/settings"); } } @@ -224,7 +219,7 @@ namespace Bit.App.Pages AppResources.ChangeMasterPassword, AppResources.Yes, AppResources.Cancel); if (confirmed) { - _platformUtilsService.LaunchUri("https://bitwarden.com/help/master-password/#change-your-master-password"); + _platformUtilsService.LaunchUri($"{_environmentService.GetWebVaultUrl()}/#/settings"); } } diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index 82d8b6ec0..8dc186a30 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -225,12 +225,7 @@ namespace Bit.App.Utilities private static string GetSendUrl(SendView send) { var environmentService = ServiceContainer.Resolve("environmentService"); - var webVaultUrl = environmentService.GetWebVaultUrl(); - if (webVaultUrl != null) - { - return webVaultUrl + "/#/send/" + send.AccessId + "/" + send.UrlB64Key; - } - return "https://send.bitwarden.com/#" + send.AccessId + "/" + send.UrlB64Key; + return environmentService.GetWebSendUrl() + send.AccessId + "/" + send.UrlB64Key; } public static async Task RemoveSendPasswordAsync(string sendId) diff --git a/src/Core/Abstractions/IEnvironmentService.cs b/src/Core/Abstractions/IEnvironmentService.cs index 8ce168128..06e76959a 100644 --- a/src/Core/Abstractions/IEnvironmentService.cs +++ b/src/Core/Abstractions/IEnvironmentService.cs @@ -13,7 +13,8 @@ namespace Bit.Core.Abstractions string WebVaultUrl { get; set; } string EventsUrl { get; set; } - string GetWebVaultUrl(); + string GetWebVaultUrl(bool returnNullIfDefault = false); + string GetWebSendUrl(); Task SetUrlsAsync(EnvironmentUrlData urls); Task SetUrlsFromStorageAsync(); } diff --git a/src/Core/Services/EnvironmentService.cs b/src/Core/Services/EnvironmentService.cs index b5a6e4511..e3550d9b8 100644 --- a/src/Core/Services/EnvironmentService.cs +++ b/src/Core/Services/EnvironmentService.cs @@ -1,13 +1,16 @@ -using Bit.Core.Abstractions; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; -using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Bit.Core.Services { public class EnvironmentService : IEnvironmentService { + private const string DEFAULT_WEB_VAULT_URL = "https://vault.bitwarden.com"; + private const string DEFAULT_WEB_SEND_URL = "https://send.bitwarden.com/#"; + private readonly IApiService _apiService; private readonly IStateService _stateService; @@ -27,17 +30,24 @@ namespace Bit.Core.Services public string NotificationsUrl { get; set; } public string EventsUrl { get; set; } - public string GetWebVaultUrl() + public string GetWebVaultUrl(bool returnNullIfDefault = false) { if (!string.IsNullOrWhiteSpace(WebVaultUrl)) { return WebVaultUrl; } - else if (!string.IsNullOrWhiteSpace(BaseUrl)) + + if (!string.IsNullOrWhiteSpace(BaseUrl)) { return BaseUrl; } - return null; + + return returnNullIfDefault ? (string)null : DEFAULT_WEB_VAULT_URL; + } + + public string GetWebSendUrl() + { + return GetWebVaultUrl(true) is string webVaultUrl ? $"{webVaultUrl}/#/send/" : DEFAULT_WEB_SEND_URL; } public async Task SetUrlsFromStorageAsync() From be993bcd02f5af7e4cab3eabec44c13e3aa50052 Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Thu, 24 Feb 2022 15:33:55 -0500 Subject: [PATCH 023/100] Fix for missing bio unlock on app restart (#1810) --- src/Core/Services/StateService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index ba06c70ac..e33d05f82 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -21,7 +21,7 @@ namespace Bit.Core.Services private State _state; private bool _migrationChecked; - public bool BiometricLocked { get; set; } + public bool BiometricLocked { get; set; } = true; public List AccountViews { get; set; } From f94812719df88cb566d167c4c26f1b3c563e92f0 Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Thu, 24 Feb 2022 17:13:00 -0500 Subject: [PATCH 024/100] Apply Disable Favicon setting globally to match desktop (#1811) * Apply Disable Favicon setting globally to match desktop * streamline the approach to applying global settings --- .../Pages/Settings/OptionsPageViewModel.cs | 1 - src/Core/Abstractions/IStateService.cs | 1 - src/Core/Services/StateService.cs | 43 +++++++++++-------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/App/Pages/Settings/OptionsPageViewModel.cs b/src/App/Pages/Settings/OptionsPageViewModel.cs index 63d2e86b6..4ce6d39c8 100644 --- a/src/App/Pages/Settings/OptionsPageViewModel.cs +++ b/src/App/Pages/Settings/OptionsPageViewModel.cs @@ -206,7 +206,6 @@ namespace Bit.App.Pages await _stateService.SetThemeAsync(theme); ThemeManager.SetTheme(Application.Current.Resources); _messagingService.Send("updatedTheme"); - _stateService.ApplyThemeGloballyAsync(theme).FireAndForget(); } } diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index db65c442a..ca4ee67f0 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -106,7 +106,6 @@ namespace Bit.Core.Abstractions Task SetRememberedOrgIdentifierAsync(string value); Task GetThemeAsync(string userId = null); Task SetThemeAsync(string value, string userId = null); - Task ApplyThemeGloballyAsync(string value); Task GetAddSitePromptShownAsync(string userId = null); Task SetAddSitePromptShownAsync(bool? value, string userId = null); Task GetPushInitialPromptShownAsync(); diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index e33d05f82..b980e7e0b 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -579,6 +579,9 @@ namespace Bit.Core.Services await GetDefaultStorageOptionsAsync()); var key = Constants.DisableFaviconKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); + + // TODO remove this to restore per-account DisableFavicon support + SetValueGloballyAsync(Constants.DisableFaviconKey, value, reconciledOptions).FireAndForget(); } public async Task GetDisableAutoTotpCopyAsync(string userId = null) @@ -863,26 +866,9 @@ namespace Bit.Core.Services await GetDefaultStorageOptionsAsync()); var key = Constants.ThemeKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); - } - public async Task ApplyThemeGloballyAsync(string value) - { - // TODO remove this method (ApplyThemeGlobally) to restore per-account theme support - await CheckStateAsync(); - if (_state?.Accounts == null) - { - return; - } - var activeUserId = await GetActiveUserIdAsync(); - foreach (var account in _state.Accounts) - { - var uid = account.Value?.Profile?.UserId; - // skip active user (theme already set) - if (uid != null && uid != activeUserId) - { - await SetThemeAsync(value, uid); - } - } + // TODO remove this to restore per-account Theme support + SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget(); } public async Task GetAddSitePromptShownAsync(string userId = null) @@ -1185,6 +1171,25 @@ namespace Bit.Core.Services await GetStorageService(options).SaveAsync(key, value); } + private async Task SetValueGloballyAsync(Func keyPrefix, T value, StorageOptions options) + { + await CheckStateAsync(); + if (_state?.Accounts == null) + { + return; + } + // userId from options was already applied, skip those + var userIdToSkip = options.UserId; + foreach (var account in _state.Accounts) + { + var uid = account.Value?.Profile?.UserId; + if (uid != null && uid != userIdToSkip) + { + await SetValueAsync(keyPrefix(uid), value, options); + } + } + } + private IStorageService GetStorageService(StorageOptions options) { return options.UseSecureStorage.GetValueOrDefault(false) ? _secureStorageService : _storageService; From fac295c97bb8cb01af7ad0715569853a6eb45a6f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Feb 2022 12:29:47 +0100 Subject: [PATCH 025/100] Autosync the updated translations (#1812) Co-authored-by: github-actions <> --- src/App/Resources/AppResources.af.resx | 27 ++++++++ src/App/Resources/AppResources.az.resx | 27 ++++++++ src/App/Resources/AppResources.be.resx | 27 ++++++++ src/App/Resources/AppResources.bg.resx | 27 ++++++++ src/App/Resources/AppResources.bn.resx | 27 ++++++++ src/App/Resources/AppResources.bs.resx | 71 ++++++++++++++------- src/App/Resources/AppResources.ca.resx | 27 ++++++++ src/App/Resources/AppResources.cs.resx | 27 ++++++++ src/App/Resources/AppResources.da.resx | 27 ++++++++ src/App/Resources/AppResources.de.resx | 27 ++++++++ src/App/Resources/AppResources.el.resx | 27 ++++++++ src/App/Resources/AppResources.en-IN.resx | 27 ++++++++ src/App/Resources/AppResources.es.resx | 27 ++++++++ src/App/Resources/AppResources.et.resx | 27 ++++++++ src/App/Resources/AppResources.fa.resx | 27 ++++++++ src/App/Resources/AppResources.fi.resx | 27 ++++++++ src/App/Resources/AppResources.fil.resx | 27 ++++++++ src/App/Resources/AppResources.fr.resx | 27 ++++++++ src/App/Resources/AppResources.he.resx | 27 ++++++++ src/App/Resources/AppResources.hi.resx | 27 ++++++++ src/App/Resources/AppResources.hr.resx | 27 ++++++++ src/App/Resources/AppResources.hu.resx | 27 ++++++++ src/App/Resources/AppResources.id.resx | 27 ++++++++ src/App/Resources/AppResources.it.resx | 27 ++++++++ src/App/Resources/AppResources.ja.resx | 27 ++++++++ src/App/Resources/AppResources.ka.resx | 27 ++++++++ src/App/Resources/AppResources.kn.resx | 27 ++++++++ src/App/Resources/AppResources.ko.resx | 27 ++++++++ src/App/Resources/AppResources.lv.resx | 27 ++++++++ src/App/Resources/AppResources.ml.resx | 27 ++++++++ src/App/Resources/AppResources.nb.resx | 27 ++++++++ src/App/Resources/AppResources.nl.resx | 27 ++++++++ src/App/Resources/AppResources.nn.resx | 27 ++++++++ src/App/Resources/AppResources.pl.resx | 27 ++++++++ src/App/Resources/AppResources.pt-BR.resx | 27 ++++++++ src/App/Resources/AppResources.ro.resx | 27 ++++++++ src/App/Resources/AppResources.ru.resx | 29 ++++++++- src/App/Resources/AppResources.si.resx | 27 ++++++++ src/App/Resources/AppResources.sk.resx | 27 ++++++++ src/App/Resources/AppResources.sl.resx | 27 ++++++++ src/App/Resources/AppResources.sr.resx | 43 ++++++++++--- src/App/Resources/AppResources.sv.resx | 59 ++++++++++++----- src/App/Resources/AppResources.ta.resx | 27 ++++++++ src/App/Resources/AppResources.th.resx | 27 ++++++++ src/App/Resources/AppResources.tr.resx | 51 +++++++++++---- src/App/Resources/AppResources.uk.resx | 27 ++++++++ src/App/Resources/AppResources.vi.resx | 27 ++++++++ src/App/Resources/AppResources.zh-Hant.resx | 37 +++++++++-- 48 files changed, 1360 insertions(+), 64 deletions(-) diff --git a/src/App/Resources/AppResources.af.resx b/src/App/Resources/AppResources.af.resx index 9068877b6..09b687e58 100644 --- a/src/App/Resources/AppResources.af.resx +++ b/src/App/Resources/AppResources.af.resx @@ -275,6 +275,18 @@ Is u seker u wil uitteken? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Hoofwagwoord Label for a master password. @@ -2092,6 +2104,21 @@ Een of meer organisasiebeleide verhoed u om u persoonlike kluis uit te stuur. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Skrap rekening diff --git a/src/App/Resources/AppResources.az.resx b/src/App/Resources/AppResources.az.resx index 1d73a97e1..f303cd14e 100644 --- a/src/App/Resources/AppResources.az.resx +++ b/src/App/Resources/AppResources.az.resx @@ -275,6 +275,18 @@ Çıxış etmək istədiyinizə əminsiniz? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Ana parol Label for a master password. @@ -2092,6 +2104,21 @@ Bir və ya daha çox təşkilat siyasəti, fərdi anbarınızı ixrac etməyinizin qarşısını alır. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Hesabı sil diff --git a/src/App/Resources/AppResources.be.resx b/src/App/Resources/AppResources.be.resx index 05afd0783..b59df1903 100644 --- a/src/App/Resources/AppResources.be.resx +++ b/src/App/Resources/AppResources.be.resx @@ -275,6 +275,18 @@ Вы ўпэўнены, што хочаце выйсці? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Асноўны пароль Label for a master password. @@ -2092,6 +2104,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.bg.resx b/src/App/Resources/AppResources.bg.resx index 1eecdafa7..556291e62 100644 --- a/src/App/Resources/AppResources.bg.resx +++ b/src/App/Resources/AppResources.bg.resx @@ -275,6 +275,18 @@ Сигурни ли сте, че искате да се отпишете? + + Премахване на регистрацията + + + Наистина ли искате да премахнете тази регистрация? + + + Регистрацията вече е добавена + + + Искате ли да превключите към нея сега? + Главна парола Label for a master password. @@ -2093,6 +2105,21 @@ Една или повече от настройките на организацията Ви не позволяват да изнасяте личния си трезор. + + Добавяне на регистрация + + + Отключено + + + Заключено + + + Отписано + + + Превключено към следващата налична регистрация + Изтриване на регистрацията diff --git a/src/App/Resources/AppResources.bn.resx b/src/App/Resources/AppResources.bn.resx index bcf537a08..5f2916b24 100644 --- a/src/App/Resources/AppResources.bn.resx +++ b/src/App/Resources/AppResources.bn.resx @@ -275,6 +275,18 @@ আপনি লগ আউট করতে চান? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + প্রধান পাসওয়ার্ড Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.bs.resx b/src/App/Resources/AppResources.bs.resx index a5ee120a5..eeabd2f6c 100644 --- a/src/App/Resources/AppResources.bs.resx +++ b/src/App/Resources/AppResources.bs.resx @@ -164,7 +164,7 @@ Title for page that we use to give credit to resources that we use. - Obriši + Izbriši Delete an entity (verb). @@ -186,37 +186,37 @@ Short label for an email address. - Adresa e-pošte + E-Mail adresa Full label for a email address. - Pošaljite nam e-poštu + Pošaljite nam E-Mail - Pošaljite nam e-poštu direktno da biste dobili pomoć ili ostavili povratne informacije. + Pošaljite nam E-Mail direktno da biste dobili pomoć ili ostavili povratne informacije. Unesite Vaš PIN kod. - Omiljene + Omiljene stavke Title for your favorite items in the vault. - Podnesite izveštaj o grešci + Podnesite izvještaj o greški - Otvorite izdanje u našem GitHub spremištu. + Prijavi problem u našem GitHub repozitoriju. - Verifikujte pomoću otiska prsta. + Upotrijebite otisak prsta da biste nastavili. Folder Label for a folder. - Kreirana je novi folder. + Kreiran je novi Folder. Folder je izbrisan. @@ -226,20 +226,20 @@ Items that have no folder specified go in this special "catch-all" folder. - Fascikle + Folderi Folder ažuriran. - Idite na vebsajt + Posjetite web stranicu The button text that allows user to launch the website to their web browser. Pomoć i povratne informacije - Sakrijte + Sakrij Hide a secret value that is currently shown (password). @@ -247,7 +247,7 @@ Description message for the alert when internet connection is required to continue. - Internet veza obavezna + Neophodna je internet veza Title for the alert when internet connection is required to continue. @@ -257,7 +257,7 @@ Nevažeći PIN. Pokušajte ponovo. - Pokrenite + Otvori web stranicu The button text that allows user to launch the website to their web browser. @@ -269,12 +269,24 @@ Title for login page. (noun) - Odjavite se + Odjava The log out button text (verb). Da li ste sigurni da želite da se odjavite? + + Obriši račun + + + Da li ste sigurni da želite ukloniti ovaj račun? + + + Račun je već dodan + + + Da li želite da se prebacite na njega sada? + Glavna lozinka Label for a master password. @@ -701,7 +713,7 @@ Otključajte pomoću PIN koda - Validacija + Potvrđivanje Message shown when interacting with the server @@ -1012,10 +1024,10 @@ April - Avgust + August - Brend + Vrsta kartice Ime vlasnika kartice @@ -1039,7 +1051,7 @@ Mjesec roka upotrebe - Godina roka upotrebe + Godina isteka Februar @@ -1099,13 +1111,13 @@ Septembar - JMBG + Broj socijalnog osiguranja / JMBG Država / Pokrajina - Titula + Naslov Poštanski broj @@ -1187,7 +1199,7 @@ Skriven - Povezano + Povezano sa Tekst @@ -2092,6 +2104,21 @@ Jedno ili više pravila organizacija onemogućuje izvoz osobnog trezora. + + Dodaj račun + + + Otključano + + + Zaključano + + + Odjavljeno + + + Prebačeni ste na sljedeći dostupan račun + Obriši račun diff --git a/src/App/Resources/AppResources.ca.resx b/src/App/Resources/AppResources.ca.resx index eb214940d..864a99fbd 100644 --- a/src/App/Resources/AppResources.ca.resx +++ b/src/App/Resources/AppResources.ca.resx @@ -275,6 +275,18 @@ Segur que voleu tancar la sessió? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Contrasenya mestra Label for a master password. @@ -2092,6 +2104,21 @@ Una o més polítiques d'organització us impedeixen exportar la vostra caixa forta. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Suprimeix el compte diff --git a/src/App/Resources/AppResources.cs.resx b/src/App/Resources/AppResources.cs.resx index a216c4bf9..64340bcb4 100644 --- a/src/App/Resources/AppResources.cs.resx +++ b/src/App/Resources/AppResources.cs.resx @@ -275,6 +275,18 @@ Opravdu se chcete odhlásit? + + Odebrat účet + + + Opravdu si přejete tento účet odebrat? + + + Účet byl již přidán + + + Would you like to switch to it now? + Hlavní heslo Label for a master password. @@ -2092,6 +2104,21 @@ Jedna nebo více zásad organizace vám brání v exportu vašeho osobního trezoru. + + Přidat účet + + + Odemčen + + + Uzamčen + + + Odhlášen + + + Přepnuto na další dostupný účet + Odstranit účet diff --git a/src/App/Resources/AppResources.da.resx b/src/App/Resources/AppResources.da.resx index cdc311e50..92276f0b1 100644 --- a/src/App/Resources/AppResources.da.resx +++ b/src/App/Resources/AppResources.da.resx @@ -275,6 +275,18 @@ Er du sikker på, at du vil logge ud? + + Fjern konto + + + Er du sikker på, at du vil fjerne denne konto? + + + Konto allerede tilføjet + + + Vil du skifte til den nu? + Hovedadgangskode Label for a master password. @@ -2092,6 +2104,21 @@ En eller flere organisationspolitikker forhindrer eksport af din personlige boks. + + Tilføj konto + + + Låst op + + + Låst + + + Logget ud + + + Skiftede til næste tilgængelige konto + Slet konto diff --git a/src/App/Resources/AppResources.de.resx b/src/App/Resources/AppResources.de.resx index 0a46eff61..a4e9cf463 100644 --- a/src/App/Resources/AppResources.de.resx +++ b/src/App/Resources/AppResources.de.resx @@ -275,6 +275,18 @@ Bist du sicher, dass du dich abmelden möchtest? + + Konto entfernen + + + Möchten Sie das Konto wirklich entfernen? + + + Konto bereits hinzugefügt + + + Möchten Sie jetzt darauf umschalten? + Masterpasswort Label for a master password. @@ -2092,6 +2104,21 @@ Eine oder mehrere Unternehmensrichtlinien verhindern es, dass du deinen persönlichen Tresor exportieren kannst. + + Konto hinzufügen + + + Entsperrt + + + Gesperrt + + + Ausgeloggt + + + Zum nächsten verfügbaren Konto gewechselt + Konto löschen diff --git a/src/App/Resources/AppResources.el.resx b/src/App/Resources/AppResources.el.resx index 2000211d6..3084c8484 100644 --- a/src/App/Resources/AppResources.el.resx +++ b/src/App/Resources/AppResources.el.resx @@ -276,6 +276,18 @@ Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε; + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Κύριος Κωδικός Label for a master password. @@ -2093,6 +2105,21 @@ Μία ή περισσότερες οργανωτικές πολιτικές αποτρέπουν την εξαγωγή του προσωπικού vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Διαγραφή Λογαριασμού diff --git a/src/App/Resources/AppResources.en-IN.resx b/src/App/Resources/AppResources.en-IN.resx index 102239845..7f9a9735f 100644 --- a/src/App/Resources/AppResources.en-IN.resx +++ b/src/App/Resources/AppResources.en-IN.resx @@ -275,6 +275,18 @@ Are you sure you want to log out? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Master password Label for a master password. @@ -2105,6 +2117,21 @@ One or more organisation policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.es.resx b/src/App/Resources/AppResources.es.resx index 1ec655eda..07e85c841 100644 --- a/src/App/Resources/AppResources.es.resx +++ b/src/App/Resources/AppResources.es.resx @@ -275,6 +275,18 @@ ¿Estás seguro de querer cerrar sesión? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Contraseña maestra Label for a master password. @@ -2092,6 +2104,21 @@ Una o más políticas de organización impiden que usted exporte su caja fuerte personal. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.et.resx b/src/App/Resources/AppResources.et.resx index 67d92ab77..911d94786 100644 --- a/src/App/Resources/AppResources.et.resx +++ b/src/App/Resources/AppResources.et.resx @@ -275,6 +275,18 @@ Oled kindel, et soovid välja logida? + + Eemalda konto + + + Oled kindel, et soovid selle konto eemaldada? + + + Konto on juba lisatud + + + Kas soovid kohe vahetada? + Ülemparool Label for a master password. @@ -2092,6 +2104,21 @@ Üks või enam organisatsiooni poliitikat ei võimalda sul oma personaalset hoidlat eksportida. + + Lisa konto + + + Lukustamata + + + Lukustatud + + + Välja logitud + + + Vahetati järgmise saadaoleva konto peale + Kustuta konto diff --git a/src/App/Resources/AppResources.fa.resx b/src/App/Resources/AppResources.fa.resx index ec81864ac..5dd3dffcc 100644 --- a/src/App/Resources/AppResources.fa.resx +++ b/src/App/Resources/AppResources.fa.resx @@ -275,6 +275,18 @@ آیا مطمئنید که می‌خواهید خارج شوید؟ + + حذف حساب + + + آیا از حذف این حساب اطمینان دارید؟ + + + حساب قبلا اضافه شده + + + آیا می خواهید همین الان تعویض کنید؟ + کلمه عبور اصلی Label for a master password. @@ -2093,6 +2105,21 @@ یک یا چند خط مشی سازمان از صادرات گاوصندوق شخصی شما جلوگیری می کند. + + افزودن حساب کاربری + + + باز شده + + + قفل شده + + + خارج شده + + + به حساب بعدی موجود تغییر کرد + حذف حساب diff --git a/src/App/Resources/AppResources.fi.resx b/src/App/Resources/AppResources.fi.resx index a7c5b84f4..7b3562a7d 100644 --- a/src/App/Resources/AppResources.fi.resx +++ b/src/App/Resources/AppResources.fi.resx @@ -275,6 +275,18 @@ Haluatko varmasti kirjautua ulos? + + Poista tili + + + Haluatko varmasti poistaa tämän tilin? + + + Tili on jo lisätty + + + Haluatko vaihtaa siihen nyt? + Pääsalasana Label for a master password. @@ -2092,6 +2104,21 @@ Yksi tai useampi organisaation käytäntö estää henkilökohtaisen holvisi viennin. + + Lisää tili + + + Avattu + + + Lukittu + + + Kirjauduttu ulos + + + Siirry seuraavaan käytettävissä olevaan tiliin + Poista tili diff --git a/src/App/Resources/AppResources.fil.resx b/src/App/Resources/AppResources.fil.resx index f564858d5..5aa4fbda4 100644 --- a/src/App/Resources/AppResources.fil.resx +++ b/src/App/Resources/AppResources.fil.resx @@ -275,6 +275,18 @@ Are you sure you want to log out? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Master Password Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.fr.resx b/src/App/Resources/AppResources.fr.resx index 19699cdb8..cd4a98995 100644 --- a/src/App/Resources/AppResources.fr.resx +++ b/src/App/Resources/AppResources.fr.resx @@ -275,6 +275,18 @@ Êtes-vous sûr de vouloir vous déconnecter ? + + Retirer le compte + + + Êtes-vous sûr de vouloir retirer ce compte ? + + + Le compte a déjà été ajouté + + + Souhaitez-vous y passer maintenant ? + Mot de passe maître Label for a master password. @@ -2092,6 +2104,21 @@ Une ou plusieurs politiques d'organisation vous empêchent d'exporter votre coffre personnel. + + Ajouter un compte + + + Déverrouillé + + + Verrouillé + + + Déconnecté + + + Passage au prochain compte disponible + Supprimer le compte diff --git a/src/App/Resources/AppResources.he.resx b/src/App/Resources/AppResources.he.resx index 7d6e9d217..5e7a0b3b9 100644 --- a/src/App/Resources/AppResources.he.resx +++ b/src/App/Resources/AppResources.he.resx @@ -275,6 +275,18 @@ האם אתה בטוח שברצונך להתנתק? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + סיסמה ראשית Label for a master password. @@ -2099,6 +2111,21 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". מדיניות אחת או יותר של הארגון שלך מונעות ממך לייצא את הכספת האישית שלך. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.hi.resx b/src/App/Resources/AppResources.hi.resx index 4eb028bc1..bec536a81 100644 --- a/src/App/Resources/AppResources.hi.resx +++ b/src/App/Resources/AppResources.hi.resx @@ -275,6 +275,18 @@ क्या आप वाकई लॉग आउट करना चाहते हैं? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + मास्टर / मुख्य पासवर्ड Label for a master password. @@ -2094,6 +2106,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.hr.resx b/src/App/Resources/AppResources.hr.resx index 28a15b358..553dca1bb 100644 --- a/src/App/Resources/AppResources.hr.resx +++ b/src/App/Resources/AppResources.hr.resx @@ -275,6 +275,18 @@ Sigurno se želiš odjaviti? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Glavna lozinka Label for a master password. @@ -2092,6 +2104,21 @@ Jedno ili više pravila organizacija onemogućuje izvoz osobnog trezora. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Obriši račun diff --git a/src/App/Resources/AppResources.hu.resx b/src/App/Resources/AppResources.hu.resx index 6842ad4be..4d5af2968 100644 --- a/src/App/Resources/AppResources.hu.resx +++ b/src/App/Resources/AppResources.hu.resx @@ -275,6 +275,18 @@ Biztos, hogy ki szeretnél jelentkezni? + + Fiók eltávolítása + + + Biztosan törlésre kerüljön ez a fiók? + + + A fiók már hozzáadásra került. + + + Szeretnénk most átkapcsolni erre? + Mesterjelszó Label for a master password. @@ -2092,6 +2104,21 @@ Egy vagy több szervezeti házirend tiltja a személyes széf exportálását. + + Fiók hozzáadása + + + Feloldva + + + Lezárva + + + Megtörtént a kijelentkezés. + + + Megtörtént az átkapcsolás a következő elérhető fiókra. + Fiók törlése diff --git a/src/App/Resources/AppResources.id.resx b/src/App/Resources/AppResources.id.resx index 33360ee67..fc686da12 100644 --- a/src/App/Resources/AppResources.id.resx +++ b/src/App/Resources/AppResources.id.resx @@ -275,6 +275,18 @@ Anda yakin ingin keluar? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Kata Sandi Utama Label for a master password. @@ -2092,6 +2104,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.it.resx b/src/App/Resources/AppResources.it.resx index c3986951e..d5d27b072 100644 --- a/src/App/Resources/AppResources.it.resx +++ b/src/App/Resources/AppResources.it.resx @@ -275,6 +275,18 @@ Sei sicuro di volerti disconnettere? + + Rimuovi account + + + Sei sicuro di voler rimuovere questo account? + + + Account già aggiunto + + + Vuoi passarci adesso? + Password principale Label for a master password. @@ -2093,6 +2105,21 @@ Una o più policy dell'organizzazione ti impediscono di esportare la tua cassaforte personale. + + Aggiungi account + + + Sbloccato + + + Bloccato + + + Disconnesso + + + Passato all'account successivo disponibile + Elimina account diff --git a/src/App/Resources/AppResources.ja.resx b/src/App/Resources/AppResources.ja.resx index c3e36c035..9f7553904 100644 --- a/src/App/Resources/AppResources.ja.resx +++ b/src/App/Resources/AppResources.ja.resx @@ -275,6 +275,18 @@ ログアウトしてもよろしいですか? + + アカウントを削除 + + + このアカウントを削除してもよろしいですか? + + + アカウントは追加済みです + + + 今すぐ切り替えますか? + マスターパスワード Label for a master password. @@ -2092,6 +2104,21 @@ 1 つまたは複数の組織ポリシーにより、個人の保管庫をエクスポートできません。 + + アカウントを追加 + + + ロック解除済み + + + ロック済み + + + ログアウト済み + + + 次の利用可能なアカウントに切り替えました + アカウントの削除 diff --git a/src/App/Resources/AppResources.ka.resx b/src/App/Resources/AppResources.ka.resx index f564858d5..5aa4fbda4 100644 --- a/src/App/Resources/AppResources.ka.resx +++ b/src/App/Resources/AppResources.ka.resx @@ -275,6 +275,18 @@ Are you sure you want to log out? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Master Password Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.kn.resx b/src/App/Resources/AppResources.kn.resx index ec82b3a3f..21950bde7 100644 --- a/src/App/Resources/AppResources.kn.resx +++ b/src/App/Resources/AppResources.kn.resx @@ -275,6 +275,18 @@ ಲಾಗ್ ಔಟ್ ಮಾಡಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.ko.resx b/src/App/Resources/AppResources.ko.resx index 57328f35b..cfbf5ad70 100644 --- a/src/App/Resources/AppResources.ko.resx +++ b/src/App/Resources/AppResources.ko.resx @@ -275,6 +275,18 @@ 정말 로그아웃하시겠습니까? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + 마스터 비밀번호 Label for a master password. @@ -2092,6 +2104,21 @@ 하나 이상의 조직 정책이 개인 보관함을 내보내는 것을 제한하고 있습니다. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.lv.resx b/src/App/Resources/AppResources.lv.resx index 634d0ac17..9585ba72f 100644 --- a/src/App/Resources/AppResources.lv.resx +++ b/src/App/Resources/AppResources.lv.resx @@ -275,6 +275,18 @@ Vai tiešām izrakstīties? + + Noņemt kontu + + + Vai tiešām noņemt šo kontu? + + + Konts jau ir pievienots + + + Vai pārslēgties uz to? + Galvenā parole Label for a master password. @@ -2092,6 +2104,21 @@ Viens vai vairāki apvienības nosacījumi neļauj izgūt privātās glabātavas saturu. + + Pievienot kontu + + + Atslēgts + + + Slēgts + + + Izrakstījies + + + Pārslēdzās uz nākamo pieejamo kontu + Dzēst kontu diff --git a/src/App/Resources/AppResources.ml.resx b/src/App/Resources/AppResources.ml.resx index faca7a664..95fc739fc 100644 --- a/src/App/Resources/AppResources.ml.resx +++ b/src/App/Resources/AppResources.ml.resx @@ -275,6 +275,18 @@ നിങ്ങൾക്ക് ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് ഉറപ്പാണോ? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + പ്രാഥമിക പാസ്‌വേഡ് Label for a master password. @@ -2092,6 +2104,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.nb.resx b/src/App/Resources/AppResources.nb.resx index 03fb9e3ce..e2ba12b81 100644 --- a/src/App/Resources/AppResources.nb.resx +++ b/src/App/Resources/AppResources.nb.resx @@ -275,6 +275,18 @@ Er du sikker på at du vil logge av? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Superpassord Label for a master password. @@ -2093,6 +2105,21 @@ En eller flere organisasjonsoppsettsregler hindrer deg i å eksportere ditt personlige hvelv. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Slett konto diff --git a/src/App/Resources/AppResources.nl.resx b/src/App/Resources/AppResources.nl.resx index 06408857d..0ea55b20a 100644 --- a/src/App/Resources/AppResources.nl.resx +++ b/src/App/Resources/AppResources.nl.resx @@ -275,6 +275,18 @@ Weet je zeker dat je wilt uitloggen? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Hoofdwachtwoord Label for a master password. @@ -2092,6 +2104,21 @@ Organisatiebeleid voorkomt dat je je persoonlijke kluis exporteert. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Account verwijderen diff --git a/src/App/Resources/AppResources.nn.resx b/src/App/Resources/AppResources.nn.resx index f564858d5..5aa4fbda4 100644 --- a/src/App/Resources/AppResources.nn.resx +++ b/src/App/Resources/AppResources.nn.resx @@ -275,6 +275,18 @@ Are you sure you want to log out? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Master Password Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.pl.resx b/src/App/Resources/AppResources.pl.resx index 6a7724509..bf566bb31 100644 --- a/src/App/Resources/AppResources.pl.resx +++ b/src/App/Resources/AppResources.pl.resx @@ -275,6 +275,18 @@ Czy na pewno chcesz się wylogować? + + Usuń konto + + + Czy na pewno chcesz usunąć to konto? + + + Konto zostało już dodane + + + Czy chcesz przełączyć się na nie teraz? + Hasło główne Label for a master password. @@ -2092,6 +2104,21 @@ Co najmniej jedna zasada organizacji uniemożliwia wyeksportowanie Twojego sejfu. + + Dodaj konto + + + Odblokowane + + + Zablokowane + + + Wylogowano + + + Przełączono na następne dostępne konto + Usuń konto diff --git a/src/App/Resources/AppResources.pt-BR.resx b/src/App/Resources/AppResources.pt-BR.resx index 234a641ce..8e44145d6 100644 --- a/src/App/Resources/AppResources.pt-BR.resx +++ b/src/App/Resources/AppResources.pt-BR.resx @@ -275,6 +275,18 @@ Tem certeza que deseja sair? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Senha Mestra Label for a master password. @@ -2093,6 +2105,21 @@ Uma ou mais políticas da organização impedem que você exporte seu cofre pessoal. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Excluir Conta diff --git a/src/App/Resources/AppResources.ro.resx b/src/App/Resources/AppResources.ro.resx index 6d74dc25f..7db363c12 100644 --- a/src/App/Resources/AppResources.ro.resx +++ b/src/App/Resources/AppResources.ro.resx @@ -275,6 +275,18 @@ Sigur doriți să vă deconectați? + + Eliminare cont + + + Sunteți sigur că doriți să eliminați acest cont? + + + Cont deja adăugat + + + Vreți să comutați la el acum? + Parolă principală Label for a master password. @@ -2092,6 +2104,21 @@ Una sau mai multe politici ale organizației vă împiedică să exportați seiful personal. + + Adăugare cont + + + Deblocat + + + Blocat + + + Deconectat + + + Comutat la următorul cont disponibil + Ștergere cont diff --git a/src/App/Resources/AppResources.ru.resx b/src/App/Resources/AppResources.ru.resx index 65d0a22f0..cce2a4f78 100644 --- a/src/App/Resources/AppResources.ru.resx +++ b/src/App/Resources/AppResources.ru.resx @@ -275,6 +275,18 @@ Вы действительно хотите выйти? + + Удалить учетную запись + + + Вы действительно хотите удалить эту учетную запись? + + + Учетная запись уже добавлена + + + Хотите переключиться на нее? + Мастер-пароль Label for a master password. @@ -2003,7 +2015,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Скрыть мой адрес электронной почты от получателей. + Скрыть мой адрес email от получателей На параметры Send влияют одна или несколько политик организации. @@ -2092,6 +2104,21 @@ Экспорт вашего личного хранилища запрещен одной или несколькими политиками организации. + + Добавить учетную запись + + + Разблокировано + + + Заблокировано + + + Вы вышли из учетной записи + + + Переключено на следующую доступную учетную запись + Удалить аккаунт diff --git a/src/App/Resources/AppResources.si.resx b/src/App/Resources/AppResources.si.resx index a88ae2186..775e41c24 100644 --- a/src/App/Resources/AppResources.si.resx +++ b/src/App/Resources/AppResources.si.resx @@ -275,6 +275,18 @@ ඔබට නික්මෙන්න අවශ්‍ය බව විශ්වාසද? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Master Password Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.sk.resx b/src/App/Resources/AppResources.sk.resx index 8ccc2ea5a..b210a279b 100644 --- a/src/App/Resources/AppResources.sk.resx +++ b/src/App/Resources/AppResources.sk.resx @@ -275,6 +275,18 @@ Naozaj sa chcete odhlásiť? + + Odstrániť účet + + + Naozaj chcete odstrániť tento účet? + + + Účet je už pridaný + + + Chceli by ste naň prejsť teraz? + Hlavné heslo Label for a master password. @@ -2092,6 +2104,21 @@ Jedna alebo viacero zásad organizácie vám bráni exportovať váš osobný trezor. + + Pridať účet + + + Odomknutý + + + Zamknutý + + + Odhlásený + + + Prepnuté na ďalší dostupný účet + Odstrániť účet diff --git a/src/App/Resources/AppResources.sl.resx b/src/App/Resources/AppResources.sl.resx index 0db8abedc..b4f3d525e 100644 --- a/src/App/Resources/AppResources.sl.resx +++ b/src/App/Resources/AppResources.sl.resx @@ -275,6 +275,18 @@ Ste prepričani, da se želite odjaviti? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Glavno geslo Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Izbriši račun diff --git a/src/App/Resources/AppResources.sr.resx b/src/App/Resources/AppResources.sr.resx index 1ea815d1e..941c0dba2 100644 --- a/src/App/Resources/AppResources.sr.resx +++ b/src/App/Resources/AppResources.sr.resx @@ -275,6 +275,18 @@ Заиста желите да се одјавите? + + Уклони налог + + + Да ли сте сигурни да желите да уклоните овај налог? + + + Налог је већ додат + + + Да ли желите да га отворите сада? + Главна Лозинка Label for a master password. @@ -2094,6 +2106,21 @@ Једна или више полиса ваше организације вас спречава да извезете ваш сеф. + + Додај налог + + + Откључано + + + Закључано + + + Одјављено + + + Пребацили сте се на следећи доступни налог + Избриши Налог @@ -2113,7 +2140,7 @@ Неисправан верификациони код. - Request one-time password + Захтевај једнократну лозинку Пошаљи код @@ -2122,24 +2149,24 @@ Слање - Copy Send link on save + Копирај линк за Send након чувања - Sending code + Слање кôда - Verifying + Проверавање - Resend Code + Понови слање кôда - A verification code was sent to your email + Верификациони кôд је послат на вашу е-пошту - An error occurred while sending a verification code to your email. Please try again + Дошло је до грешке при слању верификационог кôда на вашу е-пошту. Покушајте поново - Enter the verification code that was sent to your email + Унесите верификациони кôд који је послат на Вашу е-адресу diff --git a/src/App/Resources/AppResources.sv.resx b/src/App/Resources/AppResources.sv.resx index 13d617f6e..652b30c7c 100644 --- a/src/App/Resources/AppResources.sv.resx +++ b/src/App/Resources/AppResources.sv.resx @@ -275,6 +275,18 @@ Är du säker på att du vill logga ut? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Huvudlösenord Label for a master password. @@ -513,7 +525,7 @@ Massimportera dina objekt snabbt från andra lösenordshanterare. - Senaste synkning: + Senaste synkronisering: Längd @@ -682,7 +694,7 @@ Synkronisering misslyckades. - Synka valvet nu + Synkronisera valv nu Touch ID @@ -1388,7 +1400,7 @@ Det finns inga samlingar att visa. - {0} flyttade till {1}. + {0} flyttades till {1}. ex: Item moved to Organization. @@ -1834,15 +1846,15 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Alla försändelser + All Sends 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Försändelser + Sends 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Ett användarvänligt namn för att beskriva denna försändelse. + A friendly name to describe this Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1852,7 +1864,7 @@ Texten du vill skicka. - Dölj texten som standard när försändelsen öppnas + When accessing the Send, hide the text by default 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1868,7 +1880,7 @@ Raderingstid - Försändelsen kommer att raderas permanent på angivet datum och tid. + The Send will be permanently deleted on the specified date and time. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1881,7 +1893,7 @@ Utgångstid - Om angivet kommer åtkomst till denna försändelse upphöra på på angivet datum och tid. + If set, access to this Send will expire on the specified date and time. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1891,7 +1903,7 @@ Maximalt antal åtkomster - Om angivet kommer användare inte längre komma åt denna försändelse när den maximala antalet åtkomster har uppnåtts. + If set, users will no longer be able to access this Send once the maximum access count is reached. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1904,7 +1916,7 @@ Nytt lösenord - Kräv ett lösenord från användare innan de kommer åt denna försändelse. + Optionally require a password for users to access this Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1924,7 +1936,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Inaktivera denna försändelse så att ingen kan komma åt den. + Disable this Send so that no one can access it 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1942,11 +1954,11 @@ Dela länk - Försändelselänk + Send link 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Sök försändelser + Search Sends 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1992,11 +2004,11 @@ Anpassad - Dela detta den här försändelsen när den har sparats. + Share this Send upon save 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - På grund av en företagspolicy kan du bara radera befintliga försändelser. + Due to an enterprise policy, you are only able to delete an existing Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2093,6 +2105,21 @@ En eller flera organisationsprinciper hindrar dig från att exportera ditt personliga valv. + + Lägg till konto + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Radera konto diff --git a/src/App/Resources/AppResources.ta.resx b/src/App/Resources/AppResources.ta.resx index 99cbc0048..cca295185 100644 --- a/src/App/Resources/AppResources.ta.resx +++ b/src/App/Resources/AppResources.ta.resx @@ -275,6 +275,18 @@ நிச்சயமாக வெளியேற விரும்புகிறீரா? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + பிரதான கடவுச்சொல் Label for a master password. @@ -2093,6 +2105,21 @@ ஒன்று அ மேற்பட்ட நிறுவன கொள்கைகள் உம் சொந்த பெட்டகத்தை ஏற்றுமதிசெய்வதைத் தவிர்க்கிறது. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + கணக்கை அழி diff --git a/src/App/Resources/AppResources.th.resx b/src/App/Resources/AppResources.th.resx index 6920043a5..8d2c4698b 100644 --- a/src/App/Resources/AppResources.th.resx +++ b/src/App/Resources/AppResources.th.resx @@ -275,6 +275,18 @@ คุณแน่ใจว่าต้องการออกจากระบบ + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + รหัสผ่านหลัก Label for a master password. @@ -2093,6 +2105,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.tr.resx b/src/App/Resources/AppResources.tr.resx index 7e85e6949..721563d19 100644 --- a/src/App/Resources/AppResources.tr.resx +++ b/src/App/Resources/AppResources.tr.resx @@ -275,6 +275,18 @@ Çıkmak istediğinize emin misiniz? + + Hesabı kaldır + + + Bu hesabı kaldırmak istediğinizden emin misiniz? + + + Hesap zaten eklenmiş + + + Bu hesaba geçmek ister misiniz? + Ana parola Label for a master password. @@ -1150,10 +1162,10 @@ Otomatik doldur erişilebilirlik hizmeti - Bitwarden otomatik doldurma hizmeti; hesap, kredi kartı ve kimlik bilgilerini cihazınızdaki diğer uygulamalarda doldurmaya yardımcı olmak için Android Otomatik Doldurma Sistemi'ni kullanır. + Bitwarden otomatik doldurma hizmeti, cihazınızdaki diğer uygulamalarda hesap bilgilerini doldurmak için Android Otomatik Doldurma Sistemi'ni kullanır. - Diğer uygulamalarda hesap, kredi kartı ve kimlik bilgilerinizi doldurmak için Bitwarden otomatik doldurma hizmetini kullanabilirsiniz. + Diğer uygulamalarda hesap bilgilerinizi doldurmak için Bitwarden otomatik doldurma hizmetini kullanabilirsiniz. Otomatik doldurma ayarlarını aç @@ -1923,7 +1935,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Kimsenin erişememesi için bu Send'i devre dışı bırak. + Kimsenin erişememesi için bu Send'i devre dışı bırak 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1991,7 +2003,7 @@ Özel - Bu Send'i kaydettikten sonra paylaş. + Bu Send'i kaydettikten sonra paylaş 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2003,7 +2015,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - E-posta adresimi alıcılardan gizle. + E-posta adresimi alıcılardan gizle Bir veya daha fazla kuruluş ilkesi Send seçeneklerinizi etkiliyor. @@ -2092,6 +2104,21 @@ Bir veya daha fazla kuruluş ilkesi, kişisel kasanızı dışa aktarmanızı engelliyor. + + Hesap ekle + + + Kilitli değil + + + Kilitli + + + Çıkış yapıldı + + + Bir sonraki hesaba geçildi + Hesabı sil @@ -2111,7 +2138,7 @@ Doğrulama kodu geçersiz. - Tek kullanımlık şifre iste + Tek kullanımlık parola iste Kod gönder @@ -2120,24 +2147,24 @@ Gönderiliyor - Send linkini kayıtta kopyala + Kaydederken Send bağlantısını kopyala - Kod yollanıyor + Kod gönderiliyor Doğrulanıyor - Kodu Tekrar Yolla + Kodu yeniden gönder - Epostanıza bir doğrulama kodu yollandı + E-posta adresinize doğrulama kodu gönderildi - Epostanıza doğrulama kodu yollarken bir sorun oluştu. Lütfen daha sonra tekrar deneyin + E-posta adresinize doğrulama kodu gönderilirken bir sorun oluştu. Lütfen daha sonra tekrar deneyin - Epostanıza yollanmış doğrulama kodunu girin + E-posta adresinize gönderilen doğrulama kodunu girin diff --git a/src/App/Resources/AppResources.uk.resx b/src/App/Resources/AppResources.uk.resx index ba3a75937..262393e3c 100644 --- a/src/App/Resources/AppResources.uk.resx +++ b/src/App/Resources/AppResources.uk.resx @@ -275,6 +275,18 @@ Ви дійсно хочете вийти? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Головний пароль Label for a master password. @@ -2092,6 +2104,21 @@ Одна чи декілька організаційних політик не дозволяють вам експортувати особисте сховище. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Видалити обліковий запис diff --git a/src/App/Resources/AppResources.vi.resx b/src/App/Resources/AppResources.vi.resx index 661decc8e..ed8c554c3 100644 --- a/src/App/Resources/AppResources.vi.resx +++ b/src/App/Resources/AppResources.vi.resx @@ -275,6 +275,18 @@ Bạn có chắc chắn muốn đăng xuất không? + + Remove Account + + + Are you sure you want to remove this account? + + + Account Already Added + + + Would you like to switch to it now? + Mật khẩu Label for a master password. @@ -2092,6 +2104,21 @@ One or more organization policies prevents your from exporting your personal vault. + + Add Account + + + Unlocked + + + Locked + + + Logged Out + + + Switched to next available account + Delete Account diff --git a/src/App/Resources/AppResources.zh-Hant.resx b/src/App/Resources/AppResources.zh-Hant.resx index 6b8401acc..05c84b041 100644 --- a/src/App/Resources/AppResources.zh-Hant.resx +++ b/src/App/Resources/AppResources.zh-Hant.resx @@ -275,6 +275,18 @@ 您確定要登出嗎? + + 移除帳戶 + + + 確定要移除這個帳戶嗎? + + + 帳戶已添加 + + + 您想現在就切換到它嗎? + 主密碼 Label for a master password. @@ -1150,10 +1162,10 @@ 自動填入無障礙服務 - Bitwarden 自動填入服務使用 Android 自動填入框架來幫助您將登入資料、信用卡和身分信息填入至裝置上的其他應用程式中。 + Bitwarden 自動填入服務使用 Android 自動填入框架來協助將登入信息填入至你裝置上的其他應用程式中。 - 使用 Bitwarden 自動填入服務將登入資料、信用卡和身分信息填入到其他應用程式中。 + 使用 Bitwarden 自動填入服務將登入信息填入到其他應用程式中。 打開自動填入設定 @@ -1923,7 +1935,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 停用此 Send 以阻止任何人存取它。 + 停用此 Send 以阻止任何人存取它 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1991,7 +2003,7 @@ 自訂 - 儲存時分享此 Send。 + 儲存時分享此 Send 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2003,7 +2015,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 對收件人隱藏我的電子郵件位址。 + 對收件人隱藏我的電子郵件位址 一個或多個組織原则正影響您的 Send 選項。 @@ -2092,6 +2104,21 @@ 一或多個組織策略不允許您匯出個人密碼庫。 + + 新增帳戶 + + + 已解鎖 + + + 已鎖定 + + + 已登出 + + + 已切換到下一個可用的帳戶 + 刪除帳戶 From 317e7dad9a7041473556a2d1953342e3029e91a9 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Fri, 25 Feb 2022 21:47:21 +0100 Subject: [PATCH 026/100] BEEEP: Colorize hidden custom field when value visible (#1813) --- src/App/Pages/Vault/ViewPage.xaml | 4 ++-- src/App/Pages/Vault/ViewPageViewModel.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/App/Pages/Vault/ViewPage.xaml b/src/App/Pages/Vault/ViewPage.xaml index 79d36d551..9ac3fd99c 100644 --- a/src/App/Pages/Vault/ViewPage.xaml +++ b/src/App/Pages/Vault/ViewPage.xaml @@ -579,8 +579,8 @@ Grid.Row="1" Grid.Column="0"> PasswordFormatter.FormatPassword(_field.Value); + public Command ToggleHiddenValueCommand { get; set; } public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; From 9384b3e5389f4186b3542f48c642b4eee440a335 Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Mon, 28 Feb 2022 13:02:33 -0500 Subject: [PATCH 027/100] fixed migration and account removal issues (#1818) --- src/App/Pages/Accounts/LoginPage.xaml.cs | 6 ++++-- src/Core/Services/StateMigrationService.cs | 14 +++++++------- src/Core/Services/StateService.cs | 2 ++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/App/Pages/Accounts/LoginPage.xaml.cs b/src/App/Pages/Accounts/LoginPage.xaml.cs index bde7ba0f7..e6acef02e 100644 --- a/src/App/Pages/Accounts/LoginPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginPage.xaml.cs @@ -147,8 +147,10 @@ namespace Bit.App.Pages return; } - var selection = await DisplayActionSheet(AppResources.Options, - AppResources.Cancel, null, AppResources.GetPasswordHint, AppResources.RemoveAccount); + var buttons = _email.IsEnabled ? new[] { AppResources.GetPasswordHint } + : new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount }; + var selection = await DisplayActionSheet(AppResources.Options, + AppResources.Cancel, null, buttons); if (selection == AppResources.GetPasswordHint) { diff --git a/src/Core/Services/StateMigrationService.cs b/src/Core/Services/StateMigrationService.cs index ce3c7cf92..91f74912c 100644 --- a/src/Core/Services/StateMigrationService.cs +++ b/src/Core/Services/StateMigrationService.cs @@ -326,19 +326,19 @@ namespace Bit.Core.Services return lastVersion.Value; } - // check for original v1 migration - var envUrlsLiteDb = await GetValueAsync(Storage.LiteDb, V1Keys.EnvironmentUrlsKey); - if (envUrlsLiteDb != null) + // check for v1 state + var v1EnvUrls = await GetValueAsync(Storage.LiteDb, V1Keys.EnvironmentUrlsKey); + if (v1EnvUrls != null) { // environmentUrls still in LiteDB (never migrated to prefs), this is v1 return 1; } - // check for original v2 migration - var envUrlsPrefs = await GetValueAsync(Storage.Prefs, V2Keys.EnvironmentUrlsKey); - if (envUrlsPrefs != null) + // check for v2 state + var v2UserId = await GetValueAsync(Storage.LiteDb, V2Keys.Keys_UserId); + if (v2UserId != null) { - // environmentUrls in Prefs (migrated from LiteDB), this is v2 + // standalone userId still exists (never moved to Account object), this is v2 return 2; } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index b980e7e0b..8c7162191 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1377,6 +1377,7 @@ namespace Bit.Core.Services { await CheckStateAsync(); var currentTheme = await GetThemeAsync(); + var currentDisableFavicons = await GetDisableFaviconAsync(); account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); @@ -1405,6 +1406,7 @@ namespace Bit.Core.Services account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock; } await SetThemeAsync(currentTheme, account.Profile.UserId); + await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId); state.Accounts[account.Profile.UserId] = account; await SaveStateToStorageAsync(state); From 4a508ea7a218014d5bc95982331fb5ffe0574020 Mon Sep 17 00:00:00 2001 From: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> Date: Mon, 28 Feb 2022 12:30:27 -0600 Subject: [PATCH 028/100] Added manual trigger for builds (#1819) --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e60c0884b..fa0a5e879 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,8 @@ on: - 'gh-pages' paths-ignore: - '.github/workflows/**' + workflow_dispatch: + inputs: {} jobs: cloc: From 2076c11cbdb2ce4bfa008200a419fb6e177ec310 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Mon, 28 Feb 2022 20:04:09 +0100 Subject: [PATCH 029/100] Bump target framework to netcoreapp3.1 (#1817) Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- store/google/Publisher/Publisher.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa0a5e879..144071928 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -188,7 +188,7 @@ jobs: || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || github.ref == 'refs/heads/hotfix' run: | - PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll" + PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll" CREDS_PATH="$HOME/secrets/play_creds.json" AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab" TRACK="internal" diff --git a/store/google/Publisher/Publisher.csproj b/store/google/Publisher/Publisher.csproj index c4eb120e0..bedaef882 100644 --- a/store/google/Publisher/Publisher.csproj +++ b/store/google/Publisher/Publisher.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp3.1 Bit.Publisher Debug;Release;FDroid From 34d0ecf64bf3a7f0e9b3d0fa0f87e56af66bcfdf Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Tue, 1 Mar 2022 14:04:17 -0500 Subject: [PATCH 030/100] support for per-user biometric state tracking (#1820) --- src/App/Pages/Accounts/LockPageViewModel.cs | 4 +- .../SettingsPage/SettingsPageViewModel.cs | 2 +- src/App/Utilities/AppHelpers.cs | 1 - src/Core/Abstractions/IStateService.cs | 3 +- src/Core/Models/Domain/Account.cs | 9 +++-- src/Core/Services/AuthService.cs | 2 +- src/Core/Services/StateService.cs | 37 +++++++++++++------ src/Core/Services/VaultTimeoutService.cs | 7 ++-- .../Controllers/LockPasswordViewController.cs | 4 +- 9 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index 05607ccbb..59ca8c69d 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -374,7 +374,7 @@ namespace Bit.App.Pages page.MasterPasswordEntry.Focus(); } }); - _stateService.BiometricLocked = !success; + await _stateService.SetBiometricLockedAsync(!success); if (success) { await DoContinueAsync(); @@ -393,7 +393,7 @@ namespace Bit.App.Pages private async Task DoContinueAsync() { - _stateService.BiometricLocked = false; + await _stateService.SetBiometricLockedAsync(false); _messagingService.Send("unlocked"); UnlockedAction?.Invoke(); } diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index b4f689af9..86ab1a95e 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -393,7 +393,7 @@ namespace Bit.App.Pages { await _stateService.SetBiometricUnlockAsync(null); } - _stateService.BiometricLocked = false; + await _stateService.SetBiometricLockedAsync(false); await _cryptoService.ToggleKeyAsync(); BuildList(); } diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index 8dc186a30..c11c3f2c2 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -476,7 +476,6 @@ namespace Bit.App.Utilities policyService.ClearAsync(userId), stateService.LogoutAccountAsync(userId, userInitiated)); - stateService.BiometricLocked = true; searchService.ClearIndex(); // check if we switched accounts automatically diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index ca4ee67f0..64e7e4744 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -10,7 +10,6 @@ namespace Bit.Core.Abstractions { public interface IStateService { - bool BiometricLocked { get; set; } List AccountViews { get; } Task GetActiveUserIdAsync(); Task SetActiveUserAsync(string userId); @@ -24,6 +23,8 @@ namespace Bit.Core.Abstractions Task GetEnvironmentUrlsAsync(string userId = null); Task GetBiometricUnlockAsync(string userId = null); Task SetBiometricUnlockAsync(bool? value, string userId = null); + Task GetBiometricLockedAsync(string userId = null); + Task SetBiometricLockedAsync(bool value, string userId = null); Task CanAccessPremiumAsync(string userId = null); Task GetProtectedPinAsync(string userId = null); Task SetProtectedPinAsync(string value, string userId = null); diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs index 43360c9cd..cc65e92fe 100644 --- a/src/Core/Models/Domain/Account.cs +++ b/src/Core/Models/Domain/Account.cs @@ -8,7 +8,7 @@ namespace Bit.Core.Models.Domain public AccountProfile Profile; public AccountTokens Tokens; public AccountSettings Settings; - public AccountKeys Keys; + public AccountVolatileData VolatileData; public Account() { } @@ -17,12 +17,12 @@ namespace Bit.Core.Models.Domain Profile = profile; Tokens = tokens; Settings = new AccountSettings(); - Keys = new AccountKeys(); + VolatileData = new AccountVolatileData(); } public Account(Account account) { - // Copy constructor excludes Keys (for storage) + // Copy constructor excludes VolatileData (for storage) Profile = new AccountProfile(account.Profile); Tokens = new AccountTokens(account.Tokens); Settings = new AccountSettings(account.Settings); @@ -101,10 +101,11 @@ namespace Bit.Core.Models.Domain public VaultTimeoutAction? VaultTimeoutAction; } - public class AccountKeys + public class AccountVolatileData { public SymmetricCryptoKey Key; public EncString PinProtectedKey; + public bool? BiometricLocked; } } } diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index f919f2766..3ead4281e 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -445,7 +445,7 @@ namespace Bit.Core.Services } - _stateService.BiometricLocked = false; + await _stateService.SetBiometricLockedAsync(false); _messagingService.Send("loggedIn"); return result; } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 8c7162191..16a61a341 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -21,8 +21,6 @@ namespace Bit.Core.Services private State _state; private bool _migrationChecked; - public bool BiometricLocked { get; set; } = true; - public List AccountViews { get; set; } public StateService(IStorageService storageService, IStorageService secureStorageService) @@ -204,6 +202,22 @@ namespace Bit.Core.Services var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } + + public async Task GetBiometricLockedAsync(string userId = null) + { + return (await GetAccountAsync( + ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) + ))?.VolatileData?.BiometricLocked ?? true; + } + + public async Task SetBiometricLockedAsync(bool value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultInMemoryOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.VolatileData.BiometricLocked = value; + await SaveAccountAsync(account, reconciledOptions); + } public async Task CanAccessPremiumAsync(string userId = null) { @@ -264,7 +278,7 @@ namespace Bit.Core.Services { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) - ))?.Keys?.PinProtectedKey; + ))?.VolatileData?.PinProtectedKey; } public async Task SetPinProtectedKeyAsync(EncString value, string userId = null) @@ -272,7 +286,7 @@ namespace Bit.Core.Services var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); - account.Keys.PinProtectedKey = value; + account.VolatileData.PinProtectedKey = value; await SaveAccountAsync(account, reconciledOptions); } @@ -328,7 +342,7 @@ namespace Bit.Core.Services { return (await GetAccountAsync( ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) - ))?.Keys?.Key; + ))?.VolatileData?.Key; } public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null) @@ -336,7 +350,7 @@ namespace Bit.Core.Services var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); - account.Keys.Key = value; + account.VolatileData.Key = value; await SaveAccountAsync(account, reconciledOptions); } @@ -1207,9 +1221,9 @@ namespace Bit.Core.Services // Memory if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) { - if (_state.Accounts[options.UserId].Keys == null) + if (_state.Accounts[options.UserId].VolatileData == null) { - _state.Accounts[options.UserId].Keys = new Account.AccountKeys(); + _state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData(); } return _state.Accounts[options.UserId]; } @@ -1218,9 +1232,9 @@ namespace Bit.Core.Services _state = await GetStateFromStorageAsync(); if (_state?.Accounts?.ContainsKey(options.UserId) ?? false) { - if (_state.Accounts[options.UserId].Keys == null) + if (_state.Accounts[options.UserId].VolatileData == null) { - _state.Accounts[options.UserId].Keys = new Account.AccountKeys(); + _state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData(); } return _state.Accounts[options.UserId]; } @@ -1290,7 +1304,8 @@ namespace Bit.Core.Services { _state.Accounts[userId].Tokens.AccessToken = null; _state.Accounts[userId].Tokens.RefreshToken = null; - _state.Accounts[userId].Keys.Key = null; + _state.Accounts[userId].VolatileData.Key = null; + _state.Accounts[userId].VolatileData.BiometricLocked = null; } } if (userInitiated && _state?.ActiveUserId == userId) diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index 692cd3ccd..fa74bdb20 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -60,7 +60,7 @@ namespace Bit.Core.Services if (hasKey) { var biometricSet = await IsBiometricLockSetAsync(userId); - if (biometricSet && _stateService.BiometricLocked) + if (biometricSet && await _stateService.GetBiometricLockedAsync(userId)) { return true; } @@ -158,8 +158,9 @@ namespace Bit.Core.Services if (allowSoftLock) { - _stateService.BiometricLocked = await IsBiometricLockSetAsync(); - if (_stateService.BiometricLocked) + var isBiometricLockSet = await IsBiometricLockSetAsync(userId); + await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId); + if (isBiometricLockSet) { _messagingService.Send("locked", userInitiated); _lockedCallback?.Invoke(userInitiated); diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index c2f57fd41..5f4bfcee4 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -314,7 +314,7 @@ namespace Bit.iOS.Core.Controllers var success = await _platformUtilsService.AuthenticateBiometricAsync(null, _pinLock ? AppResources.PIN : AppResources.MasterPassword, () => MasterPasswordCell.TextField.BecomeFirstResponder()); - _stateService.BiometricLocked = !success; + await _stateService.SetBiometricLockedAsync(!success); if (success) { DoContinue(); @@ -356,7 +356,7 @@ namespace Bit.iOS.Core.Controllers await _stateService.SetPasswordVerifiedAutofillAsync(true); } await EnableBiometricsIfNeeded(); - _stateService.BiometricLocked = false; + await _stateService.SetBiometricLockedAsync(false); MasterPasswordCell.TextField.ResignFirstResponder(); Success(); } From db7ca3b93e20875618f109b96f692bbb285e1cd6 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Wed, 2 Mar 2022 14:15:16 -0300 Subject: [PATCH 031/100] BEEEP: Abstract and Centralize Logging (#1663) * Abstracted App Center Logging into its own component, so that we can have it centralized in one place and we avoid checking for FDroid on all the places we want to use it * Implemented the new logger where Crashes.TrackError was being used except on some specific cases * Improved logging, added a debug logger and removed AppCenter to be used on DEBUG --- src/Android/Autofill/AutofillService.cs | 20 ++---- src/Android/MainActivity.cs | 2 +- src/Android/MainApplication.cs | 12 +++- src/Android/Services/AndroidLogService.cs | 2 +- .../AccountSwitchingOverlayView.xaml.cs | 19 ++--- .../AccountSwitchingOverlayViewModel.cs | 8 +-- .../Pages/Accounts/DeleteAccountViewModel.cs | 18 +++-- src/App/Pages/Accounts/HomePageViewModel.cs | 3 +- src/App/Pages/Accounts/LockPageViewModel.cs | 11 ++- src/App/Pages/Accounts/LoginPageViewModel.cs | 11 ++- .../Accounts/VerificationCodeViewModel.cs | 27 +++---- .../GeneratorHistoryPageViewModel.cs | 9 +-- src/App/Pages/Send/SendAddEditPage.xaml.cs | 8 +-- .../Pages/Send/SendAddEditPageViewModel.cs | 10 ++- .../Settings/ExportVaultPageViewModel.cs | 17 ++--- src/App/Pages/Vault/AddEditPageViewModel.cs | 23 +++--- .../GroupingsPage/GroupingsPageViewModel.cs | 4 +- src/App/Pages/Vault/ScanPage.xaml.cs | 9 +-- src/App/Utilities/ThemeManager.cs | 16 ++--- src/Core/Abstractions/ILogger.cs | 26 +++++++ .../{ILogService.cs => INativeLogService.cs} | 2 +- src/Core/Core.csproj | 5 ++ src/Core/Services/ConsoleLogService.cs | 2 +- src/Core/Services/Logging/DebugLogger.cs | 50 +++++++++++++ src/Core/Services/Logging/Logger.cs | 71 +++++++++++++++++++ src/Core/Services/Logging/LoggerHelper.cs | 33 +++++++++ src/Core/Services/Logging/StubLogger.cs | 21 ++++++ src/Core/Utilities/TaskExtensions.cs | 8 +-- src/iOS.Core/Utilities/iOSCoreHelpers.cs | 18 ++++- src/iOS.Core/Utilities/iOSHelpers.cs | 4 +- src/iOS/AppDelegate.cs | 2 +- .../Services/iOSPushNotificationHandler.cs | 4 +- 32 files changed, 328 insertions(+), 147 deletions(-) create mode 100644 src/Core/Abstractions/ILogger.cs rename src/Core/Abstractions/{ILogService.cs => INativeLogService.cs} (82%) create mode 100644 src/Core/Services/Logging/DebugLogger.cs create mode 100644 src/Core/Services/Logging/Logger.cs create mode 100644 src/Core/Services/Logging/LoggerHelper.cs create mode 100644 src/Core/Services/Logging/StubLogger.cs diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs index c339794bc..07171eb9e 100644 --- a/src/Android/Autofill/AutofillService.cs +++ b/src/Android/Autofill/AutofillService.cs @@ -1,4 +1,7 @@ -using Android; +using System; +using System.Collections.Generic; +using System.Linq; +using Android; using Android.App; using Android.Content; using Android.OS; @@ -9,12 +12,6 @@ using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif -using System; -using System.Collections.Generic; -using System.Linq; namespace Bit.Droid.Autofill { @@ -28,6 +25,7 @@ namespace Bit.Droid.Autofill private IVaultTimeoutService _vaultTimeoutService; private IPolicyService _policyService; private IStateService _stateService; + private LazyResolve _logger = new LazyResolve("logger"); public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) @@ -84,9 +82,7 @@ namespace Bit.Droid.Autofill } catch (Exception e) { -#if !FDROID - Crashes.TrackError(e); -#endif + _logger.Value.Exception(e); } } @@ -160,9 +156,7 @@ namespace Bit.Droid.Autofill } catch (Exception e) { -#if !FDROID - Crashes.TrackError(e); -#endif + _logger.Value.Exception(e); } } } diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index c11f8b9f7..4018dd734 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -69,7 +69,7 @@ namespace Bit.Droid Window.AddFlags(Android.Views.WindowManagerFlags.Secure); } -#if !FDROID +#if !DEBUG && !FDROID var appCenterHelper = new AppCenterHelper(_appIdService, _stateService); var appCenterTask = appCenterHelper.InitAsync(); #endif diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index ff2fd307a..aadd11564 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -53,7 +53,8 @@ namespace Bit.Droid ServiceContainer.Resolve("apiService"), ServiceContainer.Resolve("messagingService"), ServiceContainer.Resolve("platformUtilsService"), - ServiceContainer.Resolve("deviceActionService")); + ServiceContainer.Resolve("deviceActionService"), + ServiceContainer.Resolve("logger")); ServiceContainer.Register("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner); var verificationActionsFlowHelper = new VerificationActionsFlowHelper( @@ -87,7 +88,14 @@ namespace Bit.Droid private void RegisterLocalServices() { - ServiceContainer.Register("logService", new AndroidLogService()); + ServiceContainer.Register("nativeLogService", new AndroidLogService()); +#if FDROID + ServiceContainer.Register("logger", new StubLogger()); +#elif DEBUG + ServiceContainer.Register("logger", DebugLogger.Instance); +#else + ServiceContainer.Register("logger", Logger.Instance); +#endif // Note: This might cause a race condition. Investigate more. Task.Run(() => diff --git a/src/Android/Services/AndroidLogService.cs b/src/Android/Services/AndroidLogService.cs index ead61510d..8d7cdac54 100644 --- a/src/Android/Services/AndroidLogService.cs +++ b/src/Android/Services/AndroidLogService.cs @@ -3,7 +3,7 @@ using System; namespace Bit.Core.Services { - public class AndroidLogService : ILogService + public class AndroidLogService : INativeLogService { private static readonly string _tag = "BITWARDEN"; diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs index 8f213dcba..b4af21f98 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs @@ -1,9 +1,8 @@ using System; using System.Threading.Tasks; using System.Windows.Input; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif +using Bit.Core.Abstractions; +using Bit.Core.Utilities; using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; @@ -23,14 +22,14 @@ namespace Bit.App.Controls set => SetValue(MainFabProperty, value); } + readonly LazyResolve _logger = new LazyResolve("logger"); + public AccountSwitchingOverlayView() { InitializeComponent(); ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync, -#if !FDROID - onException: ex => Crashes.TrackError(ex), -#endif + onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); } @@ -110,9 +109,7 @@ namespace Bit.App.Controls } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Value.Exception(ex); } } @@ -133,9 +130,7 @@ namespace Bit.App.Controls } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Value.Exception(ex); } } } diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs index bc15fe96a..ed6f521ca 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs @@ -4,7 +4,6 @@ using System.Windows.Input; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using Microsoft.AppCenter.Crashes; using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; @@ -16,15 +15,14 @@ namespace Bit.App.Controls private readonly IMessagingService _messagingService; public AccountSwitchingOverlayViewModel(IStateService stateService, - IMessagingService messagingService) + IMessagingService messagingService, + ILogger logger) { _stateService = stateService; _messagingService = messagingService; SelectAccountCommand = new AsyncCommand(SelectAccountAsync, -#if !FDROID - onException: ex => Crashes.TrackError(ex), -#endif + onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); } diff --git a/src/App/Pages/Accounts/DeleteAccountViewModel.cs b/src/App/Pages/Accounts/DeleteAccountViewModel.cs index cab70bd38..6705e1949 100644 --- a/src/App/Pages/Accounts/DeleteAccountViewModel.cs +++ b/src/App/Pages/Accounts/DeleteAccountViewModel.cs @@ -5,9 +5,6 @@ using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif namespace Bit.App.Pages { @@ -15,11 +12,13 @@ namespace Bit.App.Pages { readonly IPlatformUtilsService _platformUtilsService; readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper; + readonly ILogger _logger; public DeleteAccountViewModel() { _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _verificationActionsFlowHelper = ServiceContainer.Resolve("verificationActionsFlowHelper"); + _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.DeleteAccount; } @@ -44,9 +43,7 @@ namespace Bit.App.Pages } catch (System.Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred); } } @@ -60,16 +57,19 @@ namespace Bit.App.Pages readonly IMessagingService _messagingService; readonly IPlatformUtilsService _platformUtilsService; readonly IDeviceActionService _deviceActionService; + readonly ILogger _logger; public DeleteAccountActionFlowExecutioner(IApiService apiService, IMessagingService messagingService, IPlatformUtilsService platformUtilsService, - IDeviceActionService deviceActionService) + IDeviceActionService deviceActionService, + ILogger logger) { _apiService = apiService; _messagingService = messagingService; _platformUtilsService = platformUtilsService; _deviceActionService = deviceActionService; + _logger = logger; } public async Task Execute(IActionFlowParmeters parameters) @@ -102,9 +102,7 @@ namespace Bit.App.Pages catch (System.Exception ex) { await _deviceActionService.HideLoadingAsync(); -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred); } } diff --git a/src/App/Pages/Accounts/HomePageViewModel.cs b/src/App/Pages/Accounts/HomePageViewModel.cs index d0f951962..bd44d5567 100644 --- a/src/App/Pages/Accounts/HomePageViewModel.cs +++ b/src/App/Pages/Accounts/HomePageViewModel.cs @@ -17,10 +17,11 @@ namespace Bit.App.Pages { _stateService = ServiceContainer.Resolve("stateService"); _messagingService = ServiceContainer.Resolve("messagingService"); + var logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.Bitwarden; - AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService) + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, logger) { AllowActiveAccountSelection = true }; diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index 59ca8c69d..a4515c01b 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -11,9 +11,6 @@ using Bit.Core.Models.Domain; using Bit.Core.Models.Request; using Bit.Core.Utilities; using Xamarin.Forms; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif namespace Bit.App.Pages { @@ -29,6 +26,7 @@ namespace Bit.App.Pages private readonly IStateService _stateService; private readonly IBiometricService _biometricService; private readonly IKeyConnectorService _keyConnectorService; + private readonly ILogger _logger; private string _email; private bool _showPassword; @@ -55,12 +53,13 @@ namespace Bit.App.Pages _stateService = ServiceContainer.Resolve("stateService"); _biometricService = ServiceContainer.Resolve("biometricService"); _keyConnectorService = ServiceContainer.Resolve("keyConnectorService"); + _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.VerifyMasterPassword; TogglePasswordCommand = new Command(TogglePassword); SubmitCommand = new Command(async () => await SubmitAsync()); - AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService) + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { AllowAddAccountRow = true, AllowActiveAccountSelection = true @@ -151,9 +150,7 @@ namespace Bit.App.Pages if (string.IsNullOrWhiteSpace(_email)) { await _vaultTimeoutService.LogOutAsync(); -#if !FDROID - Crashes.TrackError(new NullReferenceException("Email not found in storage")); -#endif + _logger.Exception(new NullReferenceException("Email not found in storage")); return; } var webVault = _environmentService.GetWebVaultUrl(true); diff --git a/src/App/Pages/Accounts/LoginPageViewModel.cs b/src/App/Pages/Accounts/LoginPageViewModel.cs index 7514e9047..709d7cc80 100644 --- a/src/App/Pages/Accounts/LoginPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginPageViewModel.cs @@ -8,9 +8,6 @@ using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif using Xamarin.Forms; namespace Bit.App.Pages @@ -25,6 +22,7 @@ namespace Bit.App.Pages private readonly IEnvironmentService _environmentService; private readonly II18nService _i18nService; private readonly IMessagingService _messagingService; + private readonly ILogger _logger; private bool _showPassword; private bool _showCancelButton; @@ -41,12 +39,13 @@ namespace Bit.App.Pages _environmentService = ServiceContainer.Resolve("environmentService"); _i18nService = ServiceContainer.Resolve("i18nService"); _messagingService = ServiceContainer.Resolve("messagingService"); + _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.Bitwarden; TogglePasswordCommand = new Command(TogglePassword); LogInCommand = new Command(async () => await LogInAsync()); - AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService) + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { AllowAddAccountRow = true, AllowActiveAccountSelection = true @@ -221,9 +220,7 @@ namespace Bit.App.Pages } catch (Exception e) { -#if !FDROID - Crashes.TrackError(e); -#endif + _logger.Exception(e); } } diff --git a/src/App/Pages/Accounts/VerificationCodeViewModel.cs b/src/App/Pages/Accounts/VerificationCodeViewModel.cs index e160a535d..7f1c62bbb 100644 --- a/src/App/Pages/Accounts/VerificationCodeViewModel.cs +++ b/src/App/Pages/Accounts/VerificationCodeViewModel.cs @@ -1,19 +1,16 @@ using System; +using System.Threading.Tasks; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using System.Threading.Tasks; -using Bit.Core.Exceptions; -using Xamarin.Forms; -using Xamarin.CommunityToolkit.ObjectModel; -using System.Windows.Input; using Bit.App.Utilities; using Bit.Core; +using Bit.Core.Abstractions; using Bit.Core.Enums; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif +using Bit.Core.Exceptions; +using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; +using Xamarin.Forms; namespace Bit.App.Pages { @@ -24,6 +21,7 @@ namespace Bit.App.Pages private readonly IUserVerificationService _userVerificationService; private readonly IApiService _apiService; private readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper; + private readonly ILogger _logger; private bool _showPassword; private string _secret, _mainActionText, _sendCodeStatus; @@ -35,6 +33,7 @@ namespace Bit.App.Pages _userVerificationService = ServiceContainer.Resolve("userVerificationService"); _apiService = ServiceContainer.Resolve("apiService"); _verificationActionsFlowHelper = ServiceContainer.Resolve("verificationActionsFlowHelper"); + _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.VerificationCode; @@ -118,9 +117,7 @@ namespace Bit.App.Pages } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); await _deviceActionService.HideLoadingAsync(); SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain; } @@ -171,9 +168,7 @@ namespace Bit.App.Pages } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); await _deviceActionService.HideLoadingAsync(); } } diff --git a/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs b/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs index e92aedde4..ab63c81b0 100644 --- a/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs +++ b/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs @@ -4,9 +4,6 @@ using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Models.Domain; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif using Xamarin.Forms; namespace Bit.App.Pages @@ -16,6 +13,7 @@ namespace Bit.App.Pages private readonly IPlatformUtilsService _platformUtilsService; private readonly IPasswordGenerationService _passwordGenerationService; private readonly IClipboardService _clipboardService; + private readonly ILogger _logger; private bool _showNoData; @@ -24,6 +22,7 @@ namespace Bit.App.Pages _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _passwordGenerationService = ServiceContainer.Resolve("passwordGenerationService"); _clipboardService = ServiceContainer.Resolve("clipboardService"); + _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.PasswordHistory; History = new ExtendedObservableCollection(); @@ -70,9 +69,7 @@ namespace Bit.App.Pages } catch (System.Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); } } } diff --git a/src/App/Pages/Send/SendAddEditPage.xaml.cs b/src/App/Pages/Send/SendAddEditPage.xaml.cs index a0b1aa2d8..3121acf0e 100644 --- a/src/App/Pages/Send/SendAddEditPage.xaml.cs +++ b/src/App/Pages/Send/SendAddEditPage.xaml.cs @@ -7,9 +7,6 @@ using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif using Xamarin.Forms; using Xamarin.Forms.PlatformConfiguration; using Xamarin.Forms.PlatformConfiguration.iOSSpecific; @@ -21,6 +18,7 @@ namespace Bit.App.Pages { private readonly IBroadcasterService _broadcasterService; private readonly IVaultTimeoutService _vaultTimeoutService; + private readonly LazyResolve _logger = new LazyResolve("logger"); private AppOptions _appOptions; private SendAddEditPageViewModel _vm; @@ -132,9 +130,7 @@ namespace Bit.App.Pages } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Value.Exception(ex); await CloseAsync(); } } diff --git a/src/App/Pages/Send/SendAddEditPageViewModel.cs b/src/App/Pages/Send/SendAddEditPageViewModel.cs index 4a213a1b1..78642e362 100644 --- a/src/App/Pages/Send/SendAddEditPageViewModel.cs +++ b/src/App/Pages/Send/SendAddEditPageViewModel.cs @@ -10,9 +10,6 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif using Xamarin.Essentials; using Xamarin.Forms; @@ -25,6 +22,7 @@ namespace Bit.App.Pages private readonly IMessagingService _messagingService; private readonly IStateService _stateService; private readonly ISendService _sendService; + private readonly ILogger _logger; private bool _sendEnabled; private bool _canAccessPremium; private bool _emailVerified; @@ -58,6 +56,8 @@ namespace Bit.App.Pages _messagingService = ServiceContainer.Resolve("messagingService"); _stateService = ServiceContainer.Resolve("stateService"); _sendService = ServiceContainer.Resolve("sendService"); + _logger = ServiceContainer.Resolve("logger"); + TogglePasswordCommand = new Command(TogglePassword); TypeOptions = new List> @@ -455,9 +455,7 @@ namespace Bit.App.Pages catch (Exception ex) { await _deviceActionService.HideLoadingAsync(); -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred); } return false; diff --git a/src/App/Pages/Settings/ExportVaultPageViewModel.cs b/src/App/Pages/Settings/ExportVaultPageViewModel.cs index 80218b834..de84447ba 100644 --- a/src/App/Pages/Settings/ExportVaultPageViewModel.cs +++ b/src/App/Pages/Settings/ExportVaultPageViewModel.cs @@ -1,17 +1,14 @@ using System; -using Bit.App.Abstractions; -using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Utilities; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Resources; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Utilities; using Bit.Core; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif using Xamarin.Forms; namespace Bit.App.Pages @@ -26,6 +23,7 @@ namespace Bit.App.Pages private readonly IKeyConnectorService _keyConnectorService; private readonly IUserVerificationService _userVerificationService; private readonly IApiService _apiService; + private readonly ILogger _logger; private int _fileFormatSelectedIndex; private string _exportWarningMessage; @@ -48,6 +46,7 @@ namespace Bit.App.Pages _keyConnectorService = ServiceContainer.Resolve("keyConnectorService"); _userVerificationService = ServiceContainer.Resolve("userVerificationService"); _apiService = ServiceContainer.Resolve("apiService"); + _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.ExportVault; TogglePasswordCommand = new Command(TogglePassword); @@ -189,9 +188,7 @@ namespace Bit.App.Pages ClearResult(); await _platformUtilsService.ShowDialogAsync(_i18nService.T("ExportVaultFailure")); System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace); -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Exception(ex); } } diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs index 9ca606a06..f71c7ced9 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/AddEditPageViewModel.cs @@ -1,21 +1,18 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Bit.App.Abstractions; +using Bit.App.Controls; using Bit.App.Models; using Bit.App.Resources; +using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Bit.App.Controls; -using Bit.Core; using Xamarin.Forms; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif namespace Bit.App.Pages { @@ -32,6 +29,8 @@ namespace Bit.App.Pages private readonly IMessagingService _messagingService; private readonly IEventService _eventService; private readonly IPolicyService _policyService; + private readonly ILogger _logger; + private CipherView _cipher; private bool _showNotesSeparator; private bool _showPassword; @@ -68,7 +67,7 @@ namespace Bit.App.Pages new KeyValuePair(UriMatchType.Exact, AppResources.Exact), new KeyValuePair(UriMatchType.Never, AppResources.Never) }; - + public AddEditPageViewModel() { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -82,6 +81,8 @@ namespace Bit.App.Pages _collectionService = ServiceContainer.Resolve("collectionService"); _eventService = ServiceContainer.Resolve("eventService"); _policyService = ServiceContainer.Resolve("policyService"); + _logger = ServiceContainer.Resolve("logger"); + GeneratePasswordCommand = new Command(GeneratePassword); TogglePasswordCommand = new Command(TogglePassword); ToggleCardNumberCommand = new Command(ToggleCardNumber); @@ -538,9 +539,7 @@ namespace Bit.App.Pages } catch(Exception genex) { -#if !FDROID - Crashes.TrackError(genex); -#endif + _logger.Exception(genex); await _deviceActionService.HideLoadingAsync(); } return false; diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 20e4ddafd..941660b37 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -45,6 +45,7 @@ namespace Bit.App.Pages private readonly IMessagingService _messagingService; private readonly IStateService _stateService; private readonly IPasswordRepromptService _passwordRepromptService; + private readonly ILogger _logger; public GroupingsPageViewModel() { @@ -58,6 +59,7 @@ namespace Bit.App.Pages _messagingService = ServiceContainer.Resolve("messagingService"); _stateService = ServiceContainer.Resolve("stateService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); + _logger = ServiceContainer.Resolve("logger"); Loading = true; PageTitle = AppResources.MyVault; @@ -69,7 +71,7 @@ namespace Bit.App.Pages }); CipherOptionsCommand = new Command(CipherOptionsAsync); - AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService) + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { AllowAddAccountRow = true }; diff --git a/src/App/Pages/Vault/ScanPage.xaml.cs b/src/App/Pages/Vault/ScanPage.xaml.cs index 30d6fb09f..cf7e7a68e 100644 --- a/src/App/Pages/Vault/ScanPage.xaml.cs +++ b/src/App/Pages/Vault/ScanPage.xaml.cs @@ -2,7 +2,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.AppCenter.Crashes; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -14,6 +15,8 @@ namespace Bit.App.Pages private CancellationTokenSource _autofocusCts; private Task _continuousAutofocusTask; + private readonly LazyResolve _logger = new LazyResolve("logger"); + public ScanPage(Action callback) { _callback = callback; @@ -61,9 +64,7 @@ namespace Bit.App.Pages catch (TaskCanceledException) { } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + _logger.Value.Exception(ex); } }, autofocusCts.Token); } diff --git a/src/App/Utilities/ThemeManager.cs b/src/App/Utilities/ThemeManager.cs index 7bc50788b..3dabf093d 100644 --- a/src/App/Utilities/ThemeManager.cs +++ b/src/App/Utilities/ThemeManager.cs @@ -1,14 +1,12 @@ using System; +using System.Linq; +using System.Threading.Tasks; using Bit.App.Models; using Bit.App.Styles; using Bit.Core.Abstractions; +using Bit.Core.Services; using Bit.Core.Utilities; using Xamarin.Forms; -using System.Linq; -using System.Threading.Tasks; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif namespace Bit.App.Utilities { @@ -76,9 +74,7 @@ namespace Bit.App.Utilities } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + LoggerHelper.LogEvenIfCantBeResolved(ex); } } @@ -168,9 +164,7 @@ namespace Bit.App.Utilities } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + LoggerHelper.LogEvenIfCantBeResolved(ex); } } } diff --git a/src/Core/Abstractions/ILogger.cs b/src/Core/Abstractions/ILogger.cs new file mode 100644 index 000000000..704fca758 --- /dev/null +++ b/src/Core/Abstractions/ILogger.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Bit.Core.Abstractions +{ + public interface ILogger + { + /// + /// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported + /// and looked into. + /// + /// A text to be used as the issue's title + /// Additional data + void Error(string message, + IDictionary extraData = null, + [CallerMemberName] string memberName = "", + [CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0); + + /// + /// Logs an exception + /// + void Exception(Exception ex); + } +} diff --git a/src/Core/Abstractions/ILogService.cs b/src/Core/Abstractions/INativeLogService.cs similarity index 82% rename from src/Core/Abstractions/ILogService.cs rename to src/Core/Abstractions/INativeLogService.cs index 8af1a806a..fdadcca37 100644 --- a/src/Core/Abstractions/ILogService.cs +++ b/src/Core/Abstractions/INativeLogService.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Abstractions { - public interface ILogService + public interface INativeLogService { void Debug(string message); void Error(string message); diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 09129dec5..6aceb3c57 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -15,6 +15,8 @@ + + @@ -31,4 +33,7 @@ + + + diff --git a/src/Core/Services/ConsoleLogService.cs b/src/Core/Services/ConsoleLogService.cs index baa292f76..492891834 100644 --- a/src/Core/Services/ConsoleLogService.cs +++ b/src/Core/Services/ConsoleLogService.cs @@ -3,7 +3,7 @@ using System; namespace Bit.Core.Services { - public class ConsoleLogService : ILogService + public class ConsoleLogService : INativeLogService { public void Debug(string message) { diff --git a/src/Core/Services/Logging/DebugLogger.cs b/src/Core/Services/Logging/DebugLogger.cs new file mode 100644 index 000000000..e70fbda80 --- /dev/null +++ b/src/Core/Services/Logging/DebugLogger.cs @@ -0,0 +1,50 @@ +#if !FDROID +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using Bit.Core.Abstractions; + +namespace Bit.Core.Services +{ + public class DebugLogger : ILogger + { + static ILogger _instance; + public static ILogger Instance + { + get + { + if (_instance is null) + { + _instance = new DebugLogger(); + } + return _instance; + } + } + + protected DebugLogger() + { + } + + public void Error(string message, IDictionary extraData = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + var classAndMethod = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}"; + var filePathAndLineNumber = $"{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}"; + + if (string.IsNullOrEmpty(message)) + { + Debug.WriteLine($"Error found in: {classAndMethod})"); + return; + } + + Debug.WriteLine($"File: {filePathAndLineNumber}"); + Debug.WriteLine($"Method: {memberName}"); + Debug.WriteLine($"Message: {message}"); + + } + + public void Exception(Exception ex) => Debug.WriteLine(ex); + } +} +#endif diff --git a/src/Core/Services/Logging/Logger.cs b/src/Core/Services/Logging/Logger.cs new file mode 100644 index 000000000..69f781435 --- /dev/null +++ b/src/Core/Services/Logging/Logger.cs @@ -0,0 +1,71 @@ +#if !FDROID +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using Bit.Core.Abstractions; +using Microsoft.AppCenter.Crashes; + +namespace Bit.Core.Services +{ + public class Logger : ILogger + { + static ILogger _instance; + public static ILogger Instance + { + get + { + if (_instance is null) + { + _instance = new Logger(); + } + return _instance; + } + } + + protected Logger() + { + } + + public void Error(string message, + IDictionary extraData = null, + [CallerMemberName] string memberName = "", + [CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0) + { + var classAndMethod = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}"; + var filePathAndLineNumber = $"{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}"; + var properties = new Dictionary + { + ["File"] = filePathAndLineNumber, + ["Method"] = memberName + }; + + var exception = new Exception(message ?? $"Error found in: {classAndMethod}"); + if (extraData == null) + { + Crashes.TrackError(exception, properties); + } + else + { + var data = properties.Concat(extraData).ToDictionary(x => x.Key, x => x.Value); + Crashes.TrackError(exception, data); + } + } + + public void Exception(Exception exception) + { + try + { + Crashes.TrackError(exception); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + } +} +#endif diff --git a/src/Core/Services/Logging/LoggerHelper.cs b/src/Core/Services/Logging/LoggerHelper.cs new file mode 100644 index 000000000..cfce40af6 --- /dev/null +++ b/src/Core/Services/Logging/LoggerHelper.cs @@ -0,0 +1,33 @@ +using System; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +#if !FDROID +using Microsoft.AppCenter.Crashes; +#endif + +namespace Bit.Core.Services +{ + public static class LoggerHelper + { + /// + /// Logs the exception even if the service can't be resolved. + /// Useful when we need to log an exception in situations where the ServiceContainer may not be initialized. + /// + /// + public static void LogEvenIfCantBeResolved(Exception ex) + { + if (ServiceContainer.Resolve("logger", true) is ILogger logger) + { + logger.Exception(ex); + } + else + { +#if !FDROID + // just in case the caller throws the exception in a moment where the logger can't be resolved + // we need to track the error as well + Crashes.TrackError(ex); +#endif + } + } + } +} diff --git a/src/Core/Services/Logging/StubLogger.cs b/src/Core/Services/Logging/StubLogger.cs new file mode 100644 index 000000000..aa3db6aff --- /dev/null +++ b/src/Core/Services/Logging/StubLogger.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Bit.Core.Abstractions; + +namespace Bit.Core.Services +{ + /// + /// A logger that does nothing, this is useful on e.g. FDroid, where we cannot use logging through AppCenter + /// + public class StubLogger : ILogger + { + public void Error(string message, IDictionary extraData = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + { + } + + public void Exception(Exception ex) + { + } + } +} diff --git a/src/Core/Utilities/TaskExtensions.cs b/src/Core/Utilities/TaskExtensions.cs index 8e3eb8a6f..2400671f6 100644 --- a/src/Core/Utilities/TaskExtensions.cs +++ b/src/Core/Utilities/TaskExtensions.cs @@ -1,8 +1,6 @@ using System; using System.Threading.Tasks; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif +using Bit.Core.Services; namespace Bit.Core.Utilities { @@ -22,9 +20,7 @@ namespace Bit.Core.Utilities } catch (Exception ex) { -#if !FDROID - Crashes.TrackError(ex); -#endif + LoggerHelper.LogEvenIfCantBeResolved(ex); onException?.Invoke(ex); } } diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index be8034106..26190c20b 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -27,17 +27,28 @@ namespace Bit.iOS.Core.Utilities public static void RegisterAppCenter() { +#if !DEBUG var appCenterHelper = new AppCenterHelper( ServiceContainer.Resolve("appIdService"), ServiceContainer.Resolve("stateService")); var appCenterTask = appCenterHelper.InitAsync(); +#endif } public static void RegisterLocalServices() { - if (ServiceContainer.Resolve("logService", true) == null) + if (ServiceContainer.Resolve("nativeLogService", true) == null) { - ServiceContainer.Register("logService", new ConsoleLogService()); + ServiceContainer.Register("nativeLogService", new ConsoleLogService()); + } + + if (ServiceContainer.Resolve("logger", true) == null) + { +#if DEBUG + ServiceContainer.Register("logger", DebugLogger.Instance); +#else + ServiceContainer.Register("logger", Logger.Instance); +#endif } var preferencesStorage = new PreferencesStorageService(AppGroupId); @@ -156,7 +167,8 @@ namespace Bit.iOS.Core.Utilities ServiceContainer.Resolve("apiService"), ServiceContainer.Resolve("messagingService"), ServiceContainer.Resolve("platformUtilsService"), - ServiceContainer.Resolve("deviceActionService")); + ServiceContainer.Resolve("deviceActionService"), + ServiceContainer.Resolve("logger")); ServiceContainer.Register("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner); var verificationActionsFlowHelper = new VerificationActionsFlowHelper( diff --git a/src/iOS.Core/Utilities/iOSHelpers.cs b/src/iOS.Core/Utilities/iOSHelpers.cs index 7bb32057e..d10cf7403 100644 --- a/src/iOS.Core/Utilities/iOSHelpers.cs +++ b/src/iOS.Core/Utilities/iOSHelpers.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices; using Bit.App.Utilities; -using Microsoft.AppCenter.Crashes; +using Bit.Core.Services; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; @@ -39,7 +39,7 @@ namespace Bit.iOS.Core.Utilities } catch (Exception e) { - Crashes.TrackError(e); + Logger.Instance.Exception(e); } finally { diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index a426a6a3b..aeedeec98 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -287,7 +287,7 @@ namespace Bit.iOS } // Migration services - ServiceContainer.Register("logService", new ConsoleLogService()); + ServiceContainer.Register("nativeLogService", new ConsoleLogService()); // Note: This might cause a race condition. Investigate more. Task.Run(() => diff --git a/src/iOS/Services/iOSPushNotificationHandler.cs b/src/iOS/Services/iOSPushNotificationHandler.cs index bfa982432..197a7fe1a 100644 --- a/src/iOS/Services/iOSPushNotificationHandler.cs +++ b/src/iOS/Services/iOSPushNotificationHandler.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics; using Bit.App.Abstractions; +using Bit.Core.Services; using Foundation; -using Microsoft.AppCenter.Crashes; using Newtonsoft.Json.Linq; using UserNotifications; using Xamarin.Forms; @@ -45,7 +45,7 @@ namespace Bit.iOS.Services } catch (Exception ex) { - Crashes.TrackError(ex); + Logger.Instance.Exception(ex); } } From 084072e4850115cdfcf8eb276d61cd510a16607a Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Wed, 2 Mar 2022 12:32:43 -0500 Subject: [PATCH 032/100] Add url encoding to data parameter (#1822) --- src/App/Utilities/AppHelpers.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index c11c3f2c2..f94540fd7 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using Bit.App.Abstractions; using Bit.App.Pages; using Bit.App.Resources; @@ -439,7 +440,7 @@ namespace Bit.App.Utilities var escaped = Uri.EscapeDataString(JsonConvert.SerializeObject(obj)); var multiByteEscaped = Regex.Replace(escaped, "%([0-9A-F]{2})", EncodeMultibyte); - return Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped)); + return WebUtility.UrlEncode(Convert.ToBase64String(Encoding.UTF8.GetBytes(multiByteEscaped))); } public static async Task LogOutAsync(string userId, bool userInitiated = false) From a33232dec0ef3a4e771cbf56e8328d18a79558ba Mon Sep 17 00:00:00 2001 From: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> Date: Thu, 3 Mar 2022 11:20:34 -0700 Subject: [PATCH 033/100] Renewed certificates and profiles (#1823) --- .../secrets/dist_autofill.mobileprovision.gpg | Bin 5176 -> 7956 bytes .../dist_bitwarden.mobileprovision.gpg | Bin 5334 -> 8393 bytes .../dist_extension.mobileprovision.gpg | Bin 5283 -> 8265 bytes .../dist_share_extension.mobileprovision.gpg | Bin 7915 -> 7913 bytes .../secrets/iphone-distribution-cert.p12.gpg | Bin 3344 -> 3315 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/.github/secrets/dist_autofill.mobileprovision.gpg b/.github/secrets/dist_autofill.mobileprovision.gpg index 25cc64b087f6b02e6fed5e65174139c86d7047f9..1143948a8734a2030e4a317c18e378ef00f064f0 100644 GIT binary patch literal 7956 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 KwpMIv;LA)I!hMne literal 5176 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 diff --git a/.github/secrets/dist_bitwarden.mobileprovision.gpg b/.github/secrets/dist_bitwarden.mobileprovision.gpg index 54bcdf62606e79af40c1fa0413834776361c4798..979b073294d587bd4977d0b178e61459ad3a1deb 100644 GIT binary patch literal 8393 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 H-;Bp{ChG0l literal 5283 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 diff --git a/.github/secrets/dist_share_extension.mobileprovision.gpg b/.github/secrets/dist_share_extension.mobileprovision.gpg index 0fad3826d0a761e9dc1de74b7d94ca0ab2c1169e..bcfac9f533bb540afc3de79cfbde8dc91f5d0a23 100644 GIT binary patch literal 7913 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 T3-Pog8gZOL1FAFYMZIH^cc@tJ literal 7915 zcmV4Fm}T0@lH!_PVWkgz(br0aZuo2KByFOsf`^dt?M?aU4c$_=4WRVl>?`q5iRR*R9}`ops$_p5hZPSWANsAeq(H{t_l?00MbzC zq1yy>^_bNd;BWpmXcX$*^TEFJ^XjKzIW(mz#O?BnedMCM!FfBLw{U?O(xwqR2Nh zlkqvTbIqA>zAV@(4TO7q@}eD1Tao{kVPz(6-&D`@%y=-snQzo_v$T&VQqO{2ib75vZ5ThoeZEDs?3?gVa0k2!oO?2PNl&5Ff5(u&*qV6?!=39Su z8iH$V+hn{6iD4K@{$qQURVCT3ar@f0h&DdT+hEq|(7&UV7yvC)1sh;A@|~$hwGFOl z_*Bn%-=5N`%eWj3NyeSS6jCu740eubCr0L{Uws2&KIw82Qla8wQXX z?y?+$g=W*l6 z204wDUPxjZ+{5>woje~vIQ*-1JXf1sRFTcvj-Ht@$QJT%aSC%jJ+6r)r zBuJ$$^!c+VQ$}l^W#l!1n|+$#QW~Nj&8}SU%pYbh6WF@Wl=vaZb|CP1e%J8NoCfV$ zu)H(zcjwyEM&YbJOiv9l=_m;I-+bhWgOP~;2=uSvWAxzr6uHJgNyWMS60M~4lB4Vm z4B0373SSdrDfo&edErwzApf1O;`0fX?9szH1p;G05H*7$*<4)Dbr~IXu%id4WM-(& z%+$_K^5mOV;(@g?{TSxO(HZuK+3KZ=0v7SpVWp!;w;G(D2(b&rI1TIe4~U2HVmj|r zNM)=!eGVs4?gTil9dgPZE}X4xyc#LFYShF=tO{Rk149=}>f~c9xS?k@QAzW7U~ARH zE55~}V-$Zsf1kss-Ym_JGPtKgUc((tVjEOB`mO6aTZ_F(3+%;L6B)8F8YRm^3`xlB z;{_}0@R7g^F`Wxf~j`#Jfyb683=cO zEDLN7$ZgOLD?sw2u!NDZyf@S6SQ8&s24>y_@z^_vti5;*%!{e>Oj{vB6+~ZDjmfes z2P9N(M2Gr+r@kcIpcic@e%XA4sw5Eo;bA`TVfN4Lb=e=%drX;LV$;`oIrHLp%eC4i z9k+lfbpxbPU}Ygfe@AE)$nq)J7I`q01DS)C4l0uYo+PsZfO;zU`-bH>>nGWqQ=l zuorW+aXLM7tL=BfM2R#82!YQ$53Sru!L5~;l)GgQeUBL#3~7fOfi-`V>JaJ%6_c77 zoSh=kKpcl&b}n9 zL%LTcID+*KnY^&gl$9&JRP@nL6VieWB-_+dA-~0G(OlQas>MU(0;Td z{2NWN4rnQncUyBW4EU*!fJ-ILts4y|o%O&c7H4FJK+e5o&kmG-3n=EakY^39VE{0H zsyu+sCRYMVFQn`vUTDxpv}QzFw{G6i|v3d*qV!%XFxMy3`9|_k1YV&abMGYY2hUHO-p+}8wmb53X!sX z=Hn#n8G{{E=~$Fk#Z`B!ZJDL!;e3pJKPg3*d^V%j0~yTpa#`{$2(VYOtZUr2cs{@? z{C7O12GWu{1LYz*099@ubHS(q%>}^saCGcb1nEHr+XNkgzJ!S{`0bB=F7g`?x8Ct( z;Jo|jvgzg2a-m&05GLvmF3qiJ8-QB?4UUy*YPKk1#zFZm_N~cb-^vb5zY+K~?2OTL9}HI0yNSc1y6f+v}Wk!*V491YBqD zTw3Z%WacOaiXMdS(z?dp8IwU5Cu4hp{VBXW`n*E^D{n4;|4|%ITHN6x8;^qL)%=bm zJWA{MGVvV#eMK?4XM@@BE|Ss)B#=;2y&h*|SvBGrL^V_$0)TpMr)32%oc4s_(6zZ} zWnt3S_`E6C4RS|j8*r${-O6CMOzoeZfc#UMQ)lGnQ}L7lhp!^gP3~asr+F!@d%^YRb9^9k(QoDh zACTpP@NI{>v1}$I6!r0S1X0s@kfcZytsFI<|^LzlMz#utdwr|xG{7bJR z$YSUvduA%ZEorkis_MY&)WjXkv(*h=qS;;Zs8TP2;Bv zEfw&6@_DV-TAeDkTlaON31OEZ_UE(E&hNs~O*WUBJ{fY2yJ>cI^=!IO(*M8?&?^^& z7?Fi9Tt)3#6|~n!@sl@5xMF*!-=NRqfVYGMuIQk5XqJ3D+h8Twj8HgFbq=ky4EEc7 zprpRRXU!}uhXS78@RpOcf*8ePH02tG>P23UJoz~m}^fj?Mua#;kP-gmn#naW) z=as4uT*qQe#fNfNP!KHpl8gtD4Y<};zhn8j!#y4e~wLd+P!k<$QBtfO(D&6Z~~eC383rba%Rmya1Nr^R1W9zR4SnDz*{B zr@q(GgEz1pR$~=hbdB(#{WhJWIz+h}!G+uvQ7=hyzv}ut!e-q76BVR)$#ocEE_H zGB-cPwdJ~Ug{a9J<8W~+Z!qoZTk=7IkTgHk4xj=dt$VTP838b|JiP)nZKwB(U?%=X&5p9gs!XtIE%QUc*z+b06BB(@z+9V zuA-_n@g^(M2hUHR`coi@($kRnuJW^vdrZ6FnPH0{&+jq)({cf4k6$5;^v=NOlOoX7 zJR-vN%~hrScHV(>{X+^P!i7vX($jO1ePc&a(&LuDU@S?r(L-j_mUqp_?0T$bUrO7f zFR6Wxb>0y-7hNq)LBY{_`GzuRHG^UNR2=E}K<7MC+QXexb)?;uv(qH*xWLz^QnmJs z4cQij_hV$4_B5r6g7vR%Zne&f6wJSq$V?=pgvxpe4@y)(jXM}<{5$n+eGlLRCht_5bY{>D-`o3lvV+Eh9oC_TsngzVSipVDqJ zN5-GE?zpQp;z4NB!Ao(aE)S;MpT*@7pKQ7==$|!XW$S5mG?__pnlG*j_$8jQ-tBFM zdSh~Uu4yvFWP`DB6~NWINWaEc37vfk{ zd4@+VgvE;fn|H-SVj{!6`*r&TXG*V-G(5ktAEm-egk(0OfhkwNgu}LbLq`+uR}uiq zb3?Lyj)4|SDOz^4;`0-w(j!w6&#A;CuYD+;E}V{(<6JI)vsRFv)f4Z!amGjrwr6rq zG@G04ag|z5`{%3x>x%C>*BnOp|HT{`w&xOW@(2UpYDtxOe@2k?2V+5%-!EB#DQ6UE zOLUYv5inIpyB*iVvO7_q{g4Z-h*uKC2>aI2#SKgRELNmEZukJnOmem|m$Rj`e8anK z20o@k&$EXq9bap(FwXwL8gi%dR|x?iDnof+$+iVvZpM^^?Yb8Cm@F_A;2rX7p^a@r z0=i`sYpt8E)aPF@O`piXP~_tO+$oZM6=Fe&6#32vL2cJ@turQs#*E8q9~v~I^CotQ z7%h&Ai&AUh$>4fwD<{P%*eob%>Ra8jZrH9MND}hh#|uqrK)cvLGUBwfe*hIt5v8Fx ztKm)-g=hSP8J3qU{{t+|LneR?O#hL^75*~b{n79=*@t2Ffa>zfL?%U^H@VmdWlsB- z_S%!&>Xc3{?hm;pSDqQJgZwtvr1GVl7`BT$g}RfSE}CWLN8wYtgbcTIz>!zwC?F1k zbU;Dh%%VU&68cj>`53ZgD^S{syOC=NNKQz;v-uKIx1x6)8*cM#{s_eUAZootlC zq{x;swYrd^0+9h><1s*wLk4-r*ViU36JUUi`+JrUgPiGsBu6fJP`4T)8D&(qKPKi7 zL`hu-2&ROSTLk@l{WC7MVMBUIgSJz0`dII|CKv(Jtq#W}#UEzlgB=M$;=+GZU?hNk z!6zu(c~~fQ?h_YloH0bjr6oHI?AW zH93p#?_rqW^e4`^N9~(F6%#^)vS`dO8BoV^Lg`$?T;wws#Sva>bq%jx6}#9<6)uji zko4a+v`>oq-;K8J%?LrBvyKygd4?V&a-48@HTtJ}l*}{96|5DV^$@YZJBO7tr;Y3$ z(`ox~JCd&P??ZSafc6Zp*n5UG)ZFZHLCdjd;Q6u9J&Qm;1f(b`hCJ;nEZM}#Y8tuFjd8}@oT z3%gRkST9dNNp&w|bKjBU@Twi+)FX<`nvY;09{SO+t#m{VKocOnMF`bh?-qA*{8x5~ z0^S5<+Se`SP^y%On~$lwur9`n90;e-)cUTeGFldk5y^(?esb9!3R2Xvf(9%C4ivgl zLxax|N{umJc3(Xr0V2tV`WKbQR8!B}Z5^9WL>~6dhn{k0+>rL*a!T*JJFWhiB`$sC zy=f*JQP@xNHZw`(GXDz5~+~X^|evbtC^>)#uPE#khrunzQ5P$cE@71dPzsgxrV2ODVz9$?YSWw z5IJgwsY${v<=3*_0ej{CR)?O(ldGa9^iUsO?J8!Eicn}BUL0E%oXIXNm`uRVRr9F4$#dr98?$ZsRF0w`(--FZ`@ zqiAoN1+Y{-g!!U*vkD4fI&Gc1g+jr=^87Js7@ImXi%{V7Zv%N}MVEYMOLkW5r*G}b zd2-I3OvyuPzm*$`CxR@vkDH8oY(kQDX&lQhyx7jHcA{;v?i;@Ia*qEUheu;XU4rEd zElpdG?5-Nzy|oC@_Fk6WYx1GBa47L(BM}PLBm823zr=`0st6+x_|a$ai+KswPzl0O z6f|Mis-!a@&_trAOJ7_CcConSK?>oKw!|kMgT4|4Pxt*zO4!)%TIK1A5%Vu~B9yGx zT6_Y%FgPG??4@1?5#I2SmmD;W-cL>Z^P_-x$Nu-CfhQOf{q+hJNK!`OHCI)S<`y=m z8oe`o7n95azbC=N19nd!+0y5xO25x-PK)k-r)L_NAe%}QVR6}IX1#k}c)w(6OxT?jH>Kf21=h5{Z1DA>8>W!`xl+(;c+n?uNumpU# z5zu0V*`bmt0kn*$Q-emBmT7(SPh|K`+Qz?um6U2!pxOfiKC=W?o5js?GOaXhGfIVN z1t!iIDqhaU$KmlEe!?@LbZ-zO*!}=M6K0ZyB#hjR?7>`^h*oPAta3Ry(dI`g`3 zbp=KgiFcqjU8>HT0t+mcIm^v_-9>dIuq^qr@Wod8=kS}X8Dq^eD#Usz)Zm>{I8x^< zx0Tz{TvZHRzo$Nmu#LSOEI}@65$PA(*mW|2h9A4%g~&=#7EV!Ipjr) zfMxZLd=}n|SI5il8ox~5L8{2N`}-pIA1B|Q@m(`Ib*F_1BX&A&%{d$vZVPxth6HYv zH}SUZKIMoFwQsZ&8fP5eydPnX<#jGWA>it@e2wBXl(F_gj_S3MR{P)?ItOVdLqsi> zSFT>}JzmimIIRioy%QbDGKlW#bQBY^VN$*IoxX6c<3hU$K=q%qEc$AsXpxIN9T0i| zv)K~xOFlUC8C23AOyv4SR8n_wS=7TKr0F95KQ=J>>M`0aYTbC zac8l{x{cnGcQI+rLirv4*C!>JK7Dw)kjXk`qZLLzeE)JLP_WAks9&Z?6P{~vstRis z*)8+q0rM`K5V^^{(h6wPUHFq5+R{&4^n@p3anK3NroW#MYg=Lsmzs_hS(>eP&TYVH zEXOUZqlJ_2vV$q1z`dZOpwfz4@t#o1iJs)>@LPfsVVr@YPiXePcthyw?-8U;%R)H| z8{Ka1GEN{xYV~xn`ETBuia~|rA&6ZK=c+w;*xF~2pksdiHhBY1sp_$(T^^q?-TO|k zS%m)qJu8*d3#(S^{f;I>l`cLIfn6BGp~)u+-$)bj>Q|pf6l#A?nXQ{e1q#D0ONjoT zQu}h)9U~&W7}RLighcV7@nG#wDF;6RP`S!<%FK|_6ncP998}L^w-4OZcKwjnw=3Z% z-!3<>cKzztf1xwlAd>(#gy*nF%8^^`jda};2w&m|*rEY$)Jzo=M2=Pa@Osl^zG5!q8iY5p5jh{EI1dQdURS)$GJ5JS{+WVg?v0I8kos)$2_*l2j0 zUhxMaas2+an|)hw11Z$*oNVb`i%;N`zvUpf^38C{CEO>n4u#o?OGJKbI%W^a%p9A( zynbP&r0jhouE!IgRgH9Xo)V6#RF1NAB7!_-8&^tuyz zV8gDodSwp2J&|Ncl}4`L3z_NnkQon;TXzv<@)!?zbX`Q5US;XukH4Cnkp!KB=?{RC zkV+X3vJc){Uzq3Vv1oY>q;x4aU)e)g%=h4@?#*dLe;ht!tONAenvSNUwk1{H8W?>M z6T+KL92yofM7^H$#A$FC28I`U6CAW0uk(iS)aB zVRVbQsb9rtNSeFL)-ITZ)57T?^}2WuGiRW)VMucMVX%}up?2~WJEG(z<)7$tq<;wQzO=Gbd*q&Gq1(b z7crJjLx7|_2P)F%R{`1imnY!q;9C2}Hy@3lCB}%w5>Ee$^(2sP$0-!J*HViMGGdBR zYvJaAF6YG9k*SO-r8rqae?D{&b7wm8L zI`h2Tm;|NEqy?82)X%)hv|MVYJmH^x@v^Q1t;cHn3adN;R`%i^m|Ml1U~bahWcc#q z7H5!IS_PR#gQH}6ZxQ@7t6WF0j?N;xYjYG$AH5g8>>w~rSL*~jX_+x7LV{O@-;MAw z8YUM$`%!O0U0i*j1(i~s9uTS!_$CKlGEJ#?K<#m>HGBt~2-`Q8duskd-meqhegM?1 VcMmb-uQS7B=gH*Hnxjl)(@k0mXb1oR diff --git a/.github/secrets/iphone-distribution-cert.p12.gpg b/.github/secrets/iphone-distribution-cert.p12.gpg index 36a5c8888c4e4526f58e88b89f67e03e19e56cb7..0fb7ebd94bfcf2b0b7e0c096a5a9e4c0ff475245 100644 GIT binary patch literal 3315 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 literal 3344 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 Date: Fri, 4 Mar 2022 01:26:35 +0100 Subject: [PATCH 034/100] Autosync the updated translations (#1825) Co-authored-by: github-actions <> --- src/App/Resources/AppResources.el.resx | 18 ++++----- src/App/Resources/AppResources.es.resx | 40 +++++++++---------- src/App/Resources/AppResources.fi.resx | 2 +- src/App/Resources/AppResources.hu.resx | 2 +- src/App/Resources/AppResources.ko.resx | 44 ++++++++++----------- src/App/Resources/AppResources.pt-BR.resx | 18 ++++----- src/App/Resources/AppResources.ta.resx | 30 +++++++------- src/App/Resources/AppResources.zh-Hant.resx | 2 +- store/apple/ta/copy.resx | 26 +++++++++--- store/google/ta/copy.resx | 26 +++++++++--- 10 files changed, 120 insertions(+), 88 deletions(-) diff --git a/src/App/Resources/AppResources.el.resx b/src/App/Resources/AppResources.el.resx index 3084c8484..e2a633a14 100644 --- a/src/App/Resources/AppResources.el.resx +++ b/src/App/Resources/AppResources.el.resx @@ -277,16 +277,16 @@ Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε; - Remove Account + Αφαίρεση λογαριασμού - Are you sure you want to remove this account? + Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτόν τον λογαριασμό? - Account Already Added + Ο Λογαριασμός Προστέθηκε Ήδη - Would you like to switch to it now? + Θα θέλατε να το αλλάξετε τώρα? Κύριος Κωδικός @@ -2106,19 +2106,19 @@ Μία ή περισσότερες οργανωτικές πολιτικές αποτρέπουν την εξαγωγή του προσωπικού vault. - Add Account + Προσθήκη Λογαριασμού - Unlocked + Ξεκλειδώθηκε - Locked + Κλειδωμένο - Logged Out + Αποσυνδεθήκατε - Switched to next available account + Μετάβαση στον επόμενο διαθέσιμο λογαριασμό Διαγραφή Λογαριασμού diff --git a/src/App/Resources/AppResources.es.resx b/src/App/Resources/AppResources.es.resx index 07e85c841..882fc665a 100644 --- a/src/App/Resources/AppResources.es.resx +++ b/src/App/Resources/AppResources.es.resx @@ -276,16 +276,16 @@ ¿Estás seguro de querer cerrar sesión? - Remove Account + Eliminar cuenta - Are you sure you want to remove this account? + ¿Está seguro que desea eliminar esta cuenta? - Account Already Added + Cuenta ya añadida - Would you like to switch to it now? + ¿Quieres cambiarlo ahora? Contraseña maestra @@ -2063,7 +2063,7 @@ Remueve Contraseña Maestra - {0} is using SSO with customer-managed encryption. Continuing will remove your Master Password from your account and require SSO to login. + {0} está usando SSO con el cifrado administrado por el cliente. Continuar eliminará su contraseña maestra de su cuenta y requerirá SSO para iniciar sesión. Si no desea eliminar su contraseña maestra, puede abandonar esta organización. @@ -2105,25 +2105,25 @@ Una o más políticas de organización impiden que usted exporte su caja fuerte personal. - Add Account + Añadir cuenta - Unlocked + Desbloqueado - Locked + Bloqueado - Logged Out + Desconectado - Switched to next available account + Cambiado a la siguiente cuenta disponible - Delete Account + Eliminar cuenta - Deleting your account is permanent + Eliminar tu cuenta es permanente Tu cuenta y todos los datos asociados serán borrados e irrecuperables. ¿Estás seguro de que quieres continuar? @@ -2138,7 +2138,7 @@ Código de verificación no válido. - Request one-time password + Solicitar contraseña de una sola vez Enviar código @@ -2147,24 +2147,24 @@ Enviando - Copy Send link on save + Copiar enlace Enviar al guardar - Sending code + Enviando código... - Verifying + Verificando... - Resend Code + Reenviar código - A verification code was sent to your email + Se ha enviado un código de verificación a tu correo electrónico - An error occurred while sending a verification code to your email. Please try again + Se ha producido un error al enviar un código de verificación a tu correo electrónico. Por favor, inténtalo de nuevo - Enter the verification code that was sent to your email + Introduce el código de verificación enviado a tu correo electrónico diff --git a/src/App/Resources/AppResources.fi.resx b/src/App/Resources/AppResources.fi.resx index 7b3562a7d..aef681084 100644 --- a/src/App/Resources/AppResources.fi.resx +++ b/src/App/Resources/AppResources.fi.resx @@ -2117,7 +2117,7 @@ Kirjauduttu ulos - Siirry seuraavaan käytettävissä olevaan tiliin + Vaihdettu seuraavaan käytettävissä olevaan tiliin Poista tili diff --git a/src/App/Resources/AppResources.hu.resx b/src/App/Resources/AppResources.hu.resx index 4d5af2968..c62c4088c 100644 --- a/src/App/Resources/AppResources.hu.resx +++ b/src/App/Resources/AppResources.hu.resx @@ -1165,7 +1165,7 @@ A Bitwarden automatikus kitöltési szolgáltatása az Android automatikus kitöltő keretrendszerét használja a bejelentkezési adatok, hitelkártyaadatok és azonosítóadatok kitöltésére az eszközén telepített más alkalmazásokban. - A Bitwarden automatikus kitöltési szolgáltatásának használatával kitöltheted a bejelentkezési, hitelkártya- és azonosítóadatokat más alkalmazásokban. + A Bitwarden automatikus kitöltési szolgáltatás használata a bejelentkezési-, hitelkártya- és azonosítóadatok kitöltéséhez más alkalmazásokban. Automatikus kitöltés beállításainak megnyitása diff --git a/src/App/Resources/AppResources.ko.resx b/src/App/Resources/AppResources.ko.resx index cfbf5ad70..b61008092 100644 --- a/src/App/Resources/AppResources.ko.resx +++ b/src/App/Resources/AppResources.ko.resx @@ -276,16 +276,16 @@ 정말 로그아웃하시겠습니까? - Remove Account + 계정 제거 - Are you sure you want to remove this account? + 정말로 이 계정을 삭제하시겠어요? - Account Already Added + 이미 추가된 계정입니다 - Would you like to switch to it now? + 해당 계정으로 전환할까요? 마스터 비밀번호 @@ -2105,40 +2105,40 @@ 하나 이상의 조직 정책이 개인 보관함을 내보내는 것을 제한하고 있습니다. - Add Account + 계정 추가 - Unlocked + 잠금 해제됨 - Locked + 잠김 - Logged Out + 로그아웃됨 - Switched to next available account + 사용 가능한 다음 계정으로 전환함 - Delete Account + 계정 삭제 - Deleting your account is permanent + 계정 삭제는 영구적입니다 - Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue? + 계정과 모든 관련 데이터가 지워지며 복구할 수 없습니다. 정말로 계속하실건가요? - Deleting your account + 계정을 삭제하는 중 - Your account has been permanently deleted + 계정이 영구적으로 삭제되었습니다 잘못된 검증 코드입니다. - Request one-time password + 일회용 코드(OTP) 요청하기 코드 전송 @@ -2147,24 +2147,24 @@ 전송 중 - Copy Send link on save + 저장 시에 Send 링크 복사하기 - Sending code + 코드 전송 증 - Verifying + 인증 중 - Resend Code + 코드 재전송 - A verification code was sent to your email + 인증 코드를 이메일로 보냈습니다 - An error occurred while sending a verification code to your email. Please try again + 이메일로 인증 코드를 보내는 동안 오류가 발생했습니다. 다시 시도해주세요 - Enter the verification code that was sent to your email + 이메일로 전송된 인증 코드를 입력해주세요 diff --git a/src/App/Resources/AppResources.pt-BR.resx b/src/App/Resources/AppResources.pt-BR.resx index 8e44145d6..70b1bcabb 100644 --- a/src/App/Resources/AppResources.pt-BR.resx +++ b/src/App/Resources/AppResources.pt-BR.resx @@ -276,16 +276,16 @@ Tem certeza que deseja sair? - Remove Account + Remover conta - Are you sure you want to remove this account? + Tem certeza que deseja remover essa conta? - Account Already Added + Conta já adicionada - Would you like to switch to it now? + Você gostaria de mudar para ela agora? Senha Mestra @@ -2106,19 +2106,19 @@ Uma ou mais políticas da organização impedem que você exporte seu cofre pessoal. - Add Account + Adicionar conta - Unlocked + Desbloqueada - Locked + Bloqueada - Logged Out + Desconectada - Switched to next available account + Alterada para a próxima conta disponível Excluir Conta diff --git a/src/App/Resources/AppResources.ta.resx b/src/App/Resources/AppResources.ta.resx index cca295185..4a454605d 100644 --- a/src/App/Resources/AppResources.ta.resx +++ b/src/App/Resources/AppResources.ta.resx @@ -276,16 +276,16 @@ நிச்சயமாக வெளியேற விரும்புகிறீரா? - Remove Account + கணக்கை நீக்கு - Are you sure you want to remove this account? + இக்கணக்கை நிச்சயமாக நீக்க வேண்டுமா? - Account Already Added + கணக்கு ஏற்கனவே சேர்க்கப்பட்டது - Would you like to switch to it now? + அதற்கு இப்போது நிலைமாற விரும்புகிறீரா? பிரதான கடவுச்சொல் @@ -1812,16 +1812,16 @@ அணுகல்தன்மை பயன்படுத்து - Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. When enabled, we'll display a popup when login fields are selected. + செயலிகளிலும் இணையம் முழுதுமுள்ள உமது உள்நுழைவுகளைத் தன்னிரப்ப Bitwarden அணுகல்தன்மை சேவை பயன்படுத்து. இயக்கப்பட்டால், உள்நுழைவு புலங்கள் தேர்ந்தெடுக்கப்படும்போது நாங்கள் ஒரு popup காட்டுவோம். - Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. (Requires Draw-Over to be enabled as well) + செயலிகளிலும் இணையம் முழுதுமுள்ள உமது உள்நுழைவுகளைத் தன்னிரப்ப Bitwarden அணுகல்தன்மை சேவை பயன்படுத்து. (மேலே-வரைதலும் இயங்க வேண்டும்) - Use the Bitwarden Accessibility Service to use the Autofill Quick-Action Tile, and/or show a popup using Draw-Over (if enabled). + தன்னிரப்பு விரைவுச்செயல் ஓடு பயன்படுத்த மற்றும்/அல்லது மேலே-வரைதல்(இயங்கினால்) கொண்டு popup காட்ட Bitwarden அணுகல்தன்மை சேவை பயன்படுத்து. - Required to use the Autofill Quick-Action Tile, or to augment the Autofill Service by using Draw-Over (if enabled). + தன்னிரப்பு விரைவுச்செயல் ஓடு பயன்படுத்தவோ மேலே-வரைதல்(இயங்கினால்) கொண்டு தன்னிரப்பிச் சேவையை ஆதரவுமிகுதியாக்கவோ தேவைப்படுகிறது. மேலே-வரைதல் பயன்படுத்து @@ -1830,10 +1830,10 @@ இயக்கினால், உள்நுழைவு புலங்கள் தேர்ந்தெடுக்கப்படும்போது ஒரு popup காண்பிக்க Bitwarden அணுகல்தன்மை சேவையை அனுமதிக்கிறது. - If enabled, the Bitwarden Accessibility Service will display a popup when login fields are selected to assist with auto-filling your logins. + இயக்கினால், உள்நுழைவு புலங்கள் தேர்ந்தெடுக்கப்படும்போது உமது உள்நுழைவுகளைத் தன்னிரப்ப உதவ Bitwarden அணுகல்தன்மை சேவை ஒரு popup காண்பிக்கும். - If enabled, accessibility will show a popup to augment the Autofill Service for older apps that don't support the Android Autofill Framework. + இயக்கினால், Android தன்னிரப்பி சட்டகத்தை ஆதரிக்காப் பழைய செயலிகளுக்காகத் தன்னிரப்பி சேவையை ஆதரவுமிகுதியாக்க, அணுகல்தன்மை ஒரு popup காட்டும். முனைவகக் கொள்கை காரணமாக, உருப்படிகளை உம் சொந்த பெட்டகத்தில் சேமிப்பதிலிருந்து கட்டுப்படுத்தப்படுகிறீர். உரிமை விருப்பத்தை நிறுவனத்திற்கு மாற்றிக் கிடைக்கும் தொகுப்புகளிலிருந்து தேர்வுசெய்க. @@ -2106,19 +2106,19 @@ ஒன்று அ மேற்பட்ட நிறுவன கொள்கைகள் உம் சொந்த பெட்டகத்தை ஏற்றுமதிசெய்வதைத் தவிர்க்கிறது. - Add Account + கணக்கைச் சேர் - Unlocked + பூட்டவிழ்க்கப்பட்டது - Locked + பூட்டப்பட்டது - Logged Out + விடுபதியப்பட்டது - Switched to next available account + அடுத்து கிடைத்த கணக்கிற்கு நிலைமாறியது கணக்கை அழி diff --git a/src/App/Resources/AppResources.zh-Hant.resx b/src/App/Resources/AppResources.zh-Hant.resx index 05c84b041..741381699 100644 --- a/src/App/Resources/AppResources.zh-Hant.resx +++ b/src/App/Resources/AppResources.zh-Hant.resx @@ -282,7 +282,7 @@ 確定要移除這個帳戶嗎? - 帳戶已添加 + 帳戶已加入 您想現在就切換到它嗎? diff --git a/store/apple/ta/copy.resx b/store/apple/ta/copy.resx index 60506fc0c..bcadc9c6f 100644 --- a/store/apple/ta/copy.resx +++ b/store/apple/ta/copy.resx @@ -122,15 +122,31 @@ Max 30 characters - உங்கள் எல்லா உள்நுழைவுகளையும் கடவுச்சொற்களையும் சேமிக்கவும் எல்லா சாதனங்களுக்கும் இடையில் ஒத்திசைக்கவும் Bitwarden எளிய பாதுகாப்பான வழி. Bitwarden செயலி நீட்டிப்பு சஃபாரி அல்லது குரோம் மூலம் எந்தவொரு வலைத்தளத்திலும் விரைவாக உள்நுழைய உங்களை அனுமதிக்கிறது மற்றும் நூற்றுக்கணக்கான பிற பிரபல செயலிகளால் ஆதரிக்கப்படுகிறது. + Bitwarden, Inc. 8Bit Solutions LLC இன் தாய் நிறுவனம். -கடவுச்சொல் திருட்டு ஒரு தீவிரமான பிரச்சினை. நீங்கள் பார்க்கும் வலைத்தளங்களும் செயலிகளும் தினந்தினம் தாக்குதலுக்கு உள்ளாகிறது. பாதுகாப்பு மீறல்கள் ஏற்பட்டு உங்கள் கடவுச்சொற்கள் திருட்டுபோகிறது. அக்கடவுச்சொற்களை மற்ற செயலிகளிலும் வலைத்தளங்களிலும் பயன்படுத்தீர்களானால், ஊடுருவர்கள் உங்கள் மின்னஞ்சல், வங்கி மற்றும் வேறு முக்கிய கணக்குகளை எளிதில் அணுக இயலும். +THE VERGE, U.S. NEWS & WORLD REPORT, CNET மற்றும் பலவற்றால் மீச்சிறந்த கடவுச்சொல் நிர்வாகி எனப் பெயரிடப்பட்டது. -நீங்கள் உருவாக்கும் ஒவ்வொரு கணக்கிற்கும் வேறுபட்ட, சீரற்று உருவாக்கப்பட்ட கடவுச்சொல்லைப் பயன்படுத்துமாறு பாதுகாப்பு நிபுணர்கள் பரிந்துரைக்கின்றனர். ஆனால் அந்த கடவுச்சொற்களை எவ்வாறு நிர்வகிப்பீர்கள்? உங்கள் கடவுச்சொற்களை உருவாக்குவது, சேமிப்பது மற்றும் அணுகுவதை Bitwarden எளிதாக்குகிறது. +வரம்பற்ற கடவுச்சொற்களை வரம்பற்ற சாதனங்களுக்கிடையே எங்கிருந்தும் நிர்வகி, தேக்கு, பாதுகாத்துவை மற்றும் பகிர்க. Bitwarden வீட்டிலும் வேலையிலும் எங்குமுள்ளரோருக்குத் திறந்த மூல கடவுச்சொல் நிர்வகிப்பு அளிக்கிறது. -பிட்வார்டன் உங்கள் உள்நுழைவுகளை உங்கள் எல்லா சாதனங்களிலும் ஒத்திசைக்கூடிய மறைகுறியாக்கப்பட்ட பெட்டகத்தில் சேமிக்கிறது. இது உங்கள் சாதனத்தை விட்டு வெளியேறுவதற்கு முன்பு முழுமையாக குறியாக்கபடுவதால் நீங்கள் மட்டுமே உங்கள் தரவை அணுக முடியும். விரும்பினாலும் Bitwarden குழுவினரால் கூட உங்கள் தரவைப் படிக்க முடியாது. உங்கள் தரவு AES-256 பிட் குறியாக்கம், சால்டட் ஹாஷிங் மற்றும் PBKDF2 SHA-256 உடன் மூடப்பட்டுள்ளது. +அடிக்கடி பார்வையிடும் எல்லா வலைத்தளங்களுக்கும் பாதுகாப்பு தேவை அடிப்படையில் வலிய,தனித்துவ,சீரற்ற கடவுச்சொற்களை உருவாக்குக. -Bitwarden திறந்த மூல மென்பொருளில் கவனம் செலுத்துகிறது. Bitwarden மூல குறியீடு கிட்ஹப்பில் ஹோஸ்ட் செய்யப்பட்டுள்ளது, மேலும் அனைவருக்கும் பிட்வார்டன் கோட்பேஸில் மதிப்பாய்வு செய்ய, தணிக்கை செய்ய மற்றும் பங்களிக்க இலவசம். +மறையாக்கிய கோப்பு மற்றும் வெற்றுரை தகவல்களை Bitwarden Send நேரடியாக எவருக்கும் விரைவாக அனுப்புகிறது. + +நீங்கள் பாதுகாப்பாக கடவுச்சொற்களை சகபாடிகளுடன் பகிரும்வண்ணம் நிறுவனங்களுக்கு Bitwarden அணிகள் மற்றும் முனைவகத் திட்டங்கள் அளிக்கிறது. + +ஏன் Bitwarden தேர்வுசெய்வது: + +உலகத்தர மறையாக்கம் +உம் தரவு பாதுகாப்பாகவும் தனிப்பட்டதாகவுமிருக்க மேம்பட்ட இறுதிக்கிறுதி மறையாக்கத்துடன் கடவுச்சொற்கள் காக்கப்பட்டுள்ளன (AES-256 நுண்மி, salted hashing, மற்றும் PBKDF2 SHA-256). + +சேரக்கட்டிய கடவுச்சொல் உருவாக்கி +அடிக்கடி பார்வையிடும் எல்லா வலைத்தளங்களுக்கும் பாதுகாப்பு தேவை அடிப்படையில் வலிய,தனித்துவ,சீரற்ற கடவுச்சொற்களை உருவாக்குக. + +உலகளாவிய மொழிபெயர்ப்புகள் +Bitwarden மொழிபெயர்ப்புகள் 40 மொழிகளிலுள்ளன மற்றும் வளர்கிறது, நம் உலக சமூகக்குழுவிற்கு நன்றி. + +குறுக்கியக்குத்தள செயலிகள் +எந்த உலாவி,கைபேசி சாதனம், அல்லது மேசைத்தள OS முதலியவற்றிலிருந்தும் Bitwarden பெட்டகத்துடன் உணர்ச்சிவச தரவை காத்துப் பகிர்க. Max 4000 characters diff --git a/store/google/ta/copy.resx b/store/google/ta/copy.resx index c4a0e2465..836b2f0ec 100644 --- a/store/google/ta/copy.resx +++ b/store/google/ta/copy.resx @@ -126,15 +126,31 @@ Max 80 characters - உங்கள் எல்லா உள்நுழைவுகளையும் கடவுச்சொற்களையும் சேமிக்கவும் எல்லா சாதனங்களுக்கும் இடையில் ஒத்திசைக்கவும் Bitwarden எளிய பாதுகாப்பான வழி. Bitwarden செயலி நீட்டிப்பு சஃபாரி அல்லது குரோம் மூலம் எந்தவொரு வலைத்தளத்திலும் விரைவாக உள்நுழைய உங்களை அனுமதிக்கிறது மற்றும் நூற்றுக்கணக்கான பிற பிரபல செயலிகளால் ஆதரிக்கப்படுகிறது. + Bitwarden, Inc. 8Bit Solutions LLC இன் தாய் நிறுவனம். -கடவுச்சொல் திருட்டு ஒரு தீவிரமான பிரச்சினை. நீங்கள் பார்க்கும் வலைத்தளங்களும் செயலிகளும் தினந்தினம் தாக்குதலுக்கு உள்ளாகிறது. பாதுகாப்பு மீறல்கள் ஏற்பட்டு உங்கள் கடவுச்சொற்கள் திருட்டுபோகிறது. அக்கடவுச்சொற்களை மற்ற செயலிகளிலும் வலைத்தளங்களிலும் பயன்படுத்தீர்களானால், ஊடுருவர்கள் உங்கள் மின்னஞ்சல், வங்கி மற்றும் வேறு முக்கிய கணக்குகளை எளிதில் அணுக இயலும். +THE VERGE, U.S. NEWS & WORLD REPORT, CNET மற்றும் பலவற்றால் மீச்சிறந்த கடவுச்சொல் நிர்வாகி எனப் பெயரிடப்பட்டது. -நீங்கள் உருவாக்கும் ஒவ்வொரு கணக்கிற்கும் வேறுபட்ட, சீரற்று உருவாக்கப்பட்ட கடவுச்சொல்லைப் பயன்படுத்துமாறு பாதுகாப்பு நிபுணர்கள் பரிந்துரைக்கின்றனர். ஆனால் அந்த கடவுச்சொற்களை எவ்வாறு நிர்வகிப்பீர்கள்? உங்கள் கடவுச்சொற்களை உருவாக்குவது, சேமிப்பது மற்றும் அணுகுவதை Bitwarden எளிதாக்குகிறது. +வரம்பற்ற கடவுச்சொற்களை வரம்பற்ற சாதனங்களுக்கிடையே எங்கிருந்தும் நிர்வகி, தேக்கு, பாதுகாத்துவை மற்றும் பகிர்க. Bitwarden வீட்டிலும் வேலையிலும் எங்குமுள்ளரோருக்குத் திறந்த மூல கடவுச்சொல் நிர்வகிப்பு அளிக்கிறது. -பிட்வார்டன் உங்கள் உள்நுழைவுகளை உங்கள் எல்லா சாதனங்களிலும் ஒத்திசைக்கூடிய மறைகுறியாக்கப்பட்ட பெட்டகத்தில் சேமிக்கிறது. இது உங்கள் சாதனத்தை விட்டு வெளியேறுவதற்கு முன்பு முழுமையாக குறியாக்கபடுவதால் நீங்கள் மட்டுமே உங்கள் தரவை அணுக முடியும். விரும்பினாலும் Bitwarden குழுவினரால் கூட உங்கள் தரவைப் படிக்க முடியாது. உங்கள் தரவு AES-256 பிட் குறியாக்கம், சால்டட் ஹாஷிங் மற்றும் PBKDF2 SHA-256 உடன் மூடப்பட்டுள்ளது. +அடிக்கடி பார்வையிடும் எல்லா வலைத்தளங்களுக்கும் பாதுகாப்பு தேவை அடிப்படையில் வலிய,தனித்துவ,சீரற்ற கடவுச்சொற்களை உருவாக்குக. -Bitwarden திறந்த மூல மென்பொருளில் கவனம் செலுத்துகிறது. Bitwarden மூல குறியீடு கிட்ஹப்பில் ஹோஸ்ட் செய்யப்பட்டுள்ளது, மேலும் அனைவருக்கும் பிட்வார்டன் கோட்பேஸில் மதிப்பாய்வு செய்ய, தணிக்கை செய்ய மற்றும் பங்களிக்க இலவசம். +மறையாக்கிய கோப்பு மற்றும் வெற்றுரை தகவல்களை Bitwarden Send நேரடியாக எவருக்கும் விரைவாக அனுப்புகிறது. + +நீங்கள் பாதுகாப்பாக கடவுச்சொற்களை சகபாடிகளுடன் பகிரும்வண்ணம் நிறுவனங்களுக்கு Bitwarden அணிகள் மற்றும் முனைவகத் திட்டங்கள் அளிக்கிறது. + +ஏன் Bitwarden தேர்வுசெய்வது: + +உலகத்தர மறையாக்கம் +உம் தரவு பாதுகாப்பாகவும் தனிப்பட்டதாகவுமிருக்க மேம்பட்ட இறுதிக்கிறுதி மறையாக்கத்துடன் கடவுச்சொற்கள் காக்கப்பட்டுள்ளன (AES-256 நுண்மி, salted hashing, மற்றும் PBKDF2 SHA-256). + +சேரக்கட்டிய கடவுச்சொல் உருவாக்கி +அடிக்கடி பார்வையிடும் எல்லா வலைத்தளங்களுக்கும் பாதுகாப்பு தேவை அடிப்படையில் வலிய,தனித்துவ,சீரற்ற கடவுச்சொற்களை உருவாக்குக. + +உலகளாவிய மொழிபெயர்ப்புகள் +Bitwarden மொழிபெயர்ப்புகள் 40 மொழிகளிலுள்ளன மற்றும் வளர்கிறது, நம் உலக சமூகக்குழுவிற்கு நன்றி. + +குறுக்கியக்குத்தள செயலிகள் +எந்த உலாவி,கைபேசி சாதனம், அல்லது மேசைத்தள OS முதலியவற்றிலிருந்தும் Bitwarden பெட்டகத்துடன் உணர்ச்சிவச தரவை காத்துப் பகிர்க. Max 4000 characters From efd83d07dd9419fe6ae5e399f23853dbb1ed14d9 Mon Sep 17 00:00:00 2001 From: stevenlele <15964380+stevenlele@users.noreply.github.com> Date: Mon, 7 Mar 2022 22:16:58 +0800 Subject: [PATCH 035/100] [SupportedBrowsers] add Alook (#1814) --- src/Android/Accessibility/AccessibilityHelpers.cs | 2 ++ src/Android/Autofill/AutofillHelpers.cs | 2 ++ src/Android/Resources/xml/autofillservice.xml | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/src/Android/Accessibility/AccessibilityHelpers.cs b/src/Android/Accessibility/AccessibilityHelpers.cs index d86d3d796..82bb3b4a5 100644 --- a/src/Android/Accessibility/AccessibilityHelpers.cs +++ b/src/Android/Accessibility/AccessibilityHelpers.cs @@ -31,6 +31,8 @@ namespace Bit.Droid.Accessibility // So keep them in sync with: // - AutofillHelpers.{TrustedBrowsers,CompatBrowsers} // - Resources/xml/autofillservice.xml + new Browser("alook.browser", "search_fragment_input_view"), + new Browser("alook.browser.google", "search_fragment_input_view"), new Browser("com.amazon.cloud9", "url"), new Browser("com.android.browser", "url"), new Browser("com.android.chrome", "url_bar"), diff --git a/src/Android/Autofill/AutofillHelpers.cs b/src/Android/Autofill/AutofillHelpers.cs index ab10aa005..4549e56b8 100644 --- a/src/Android/Autofill/AutofillHelpers.cs +++ b/src/Android/Autofill/AutofillHelpers.cs @@ -51,6 +51,8 @@ namespace Bit.Droid.Autofill // - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too. public static HashSet CompatBrowsers = new HashSet { + "alook.browser", + "alook.browser.google", "com.amazon.cloud9", "com.android.browser", "com.android.chrome", diff --git a/src/Android/Resources/xml/autofillservice.xml b/src/Android/Resources/xml/autofillservice.xml index 20432c86b..c65812815 100644 --- a/src/Android/Resources/xml/autofillservice.xml +++ b/src/Android/Resources/xml/autofillservice.xml @@ -11,6 +11,12 @@ --> + + From 79a76c46385d61fad6d707561035d15c0771d15e Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:28:06 -0500 Subject: [PATCH 036/100] Support for lock/logout/remove accounts from account list (#1826) * Support for lock/logout/remove accounts via long-press * establish and set listview height before showing * undo modification --- src/Android/Android.csproj | 2 +- src/App/App.csproj | 6 +- src/App/App.xaml.cs | 50 ++++++------ .../AccountSwitchingOverlayView.xaml | 8 +- .../AccountSwitchingOverlayView.xaml.cs | 67 ++++++++++++++-- .../AccountSwitchingOverlayViewModel.cs | 19 ++++- .../AccountViewCell/AccountViewCell.xaml | 14 +++- .../AccountViewCell/AccountViewCell.xaml.cs | 21 ++++- .../AccountViewCellViewModel.cs | 15 ++++ src/App/Pages/Accounts/HomePage.xaml | 2 + src/App/Pages/Accounts/LockPage.xaml | 2 + src/App/Pages/Accounts/LoginPage.xaml | 2 + .../Vault/GroupingsPage/GroupingsPage.xaml | 1 + src/App/Resources/AppResources.Designer.cs | 24 ++++++ src/App/Resources/AppResources.resx | 9 +++ src/App/Utilities/AppHelpers.cs | 77 ++++++++++++++++++- src/Core/Abstractions/IStateService.cs | 1 + src/Core/Abstractions/IVaultTimeoutService.cs | 2 + src/Core/Services/EnvironmentService.cs | 1 + src/Core/Services/StateService.cs | 26 ++++--- src/Core/Services/VaultTimeoutService.cs | 50 +++++++++--- src/Core/Utilities/ServiceContainer.cs | 8 +- .../iOS.ShareExtension.csproj | 2 +- src/iOS/iOS.csproj | 2 +- 24 files changed, 342 insertions(+), 69 deletions(-) diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 52e4f9646..bd3ef91fa 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -84,7 +84,7 @@ - 1.7.0 + 1.7.1 122.0.0 diff --git a/src/App/App.csproj b/src/App/App.csproj index b335c3a11..8894ca5ae 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -16,10 +16,10 @@ - - + + - + diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 0dada2232..bb0083155 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -73,7 +73,11 @@ namespace Bit.App } else if (message.Command == "locked") { - await LockedAsync(!(message.Data as bool?).GetValueOrDefault()); + var extras = message.Data as Tuple; + var userId = extras?.Item1; + var userInitiated = extras?.Item2; + Device.BeginInvokeOnMainThread(async () => + await LockedAsync(userId, userInitiated.GetValueOrDefault())); } else if (message.Command == "lockVault") { @@ -283,10 +287,9 @@ namespace Bit.App private async Task SwitchedAccountAsync() { await AppHelpers.OnAccountSwitchAsync(); - var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync(); Device.BeginInvokeOnMainThread(async () => { - if (shouldTimeout) + if (await _vaultTimeoutService.ShouldTimeoutAsync()) { await _vaultTimeoutService.ExecuteTimeoutActionAsync(); } @@ -304,24 +307,20 @@ namespace Bit.App var authed = await _stateService.IsAuthenticatedAsync(); if (authed) { - var isLocked = await _vaultTimeoutService.IsLockedAsync(); - var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync(); - if (isLocked || shouldTimeout) + if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() || + await _vaultTimeoutService.ShouldLogOutByTimeoutAsync()) { - var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync(); - if (vaultTimeoutAction == VaultTimeoutAction.Logout) - { - // TODO implement orgIdentifier flow to SSO Login page, same as email flow below - // var orgIdentifier = await _stateService.GetOrgIdentifierAsync(); - - var email = await _stateService.GetEmailAsync(); - Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null; - Current.MainPage = new NavigationPage(new LoginPage(email, Options)); - } - else - { - Current.MainPage = new NavigationPage(new LockPage(Options)); - } + // TODO implement orgIdentifier flow to SSO Login page, same as email flow below + // var orgIdentifier = await _stateService.GetOrgIdentifierAsync(); + + var email = await _stateService.GetEmailAsync(); + Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null; + Current.MainPage = new NavigationPage(new LoginPage(email, Options)); + } + else if (await _vaultTimeoutService.IsLockedAsync() || + await _vaultTimeoutService.ShouldLockAsync()) + { + Current.MainPage = new NavigationPage(new LockPage(Options)); } else if (Options.FromAutofillFramework && Options.SaveType.HasValue) { @@ -343,7 +342,8 @@ namespace Bit.App else { Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null; - if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync()) + if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() || + await _vaultTimeoutService.ShouldLogOutByTimeoutAsync()) { // TODO implement orgIdentifier flow to SSO Login page, same as email flow below // var orgIdentifier = await _stateService.GetOrgIdentifierAsync(); @@ -430,8 +430,14 @@ namespace Bit.App }); } - private async Task LockedAsync(bool autoPromptBiometric) + private async Task LockedAsync(string userId, bool autoPromptBiometric) { + if (!await _stateService.IsActiveAccount(userId)) + { + _platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully); + return; + } + if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS) { var vaultTimeout = await _stateService.GetVaultTimeoutAsync(); diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml index 0edbf4503..f0c52d0d4 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml @@ -27,15 +27,17 @@ + Account="{Binding .}" + SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}" + LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}" + /> diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs index b4af21f98..33b07ec8e 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs @@ -10,12 +10,24 @@ namespace Bit.App.Controls { public partial class AccountSwitchingOverlayView : ContentView { + public static readonly BindableProperty MainPageProperty = BindableProperty.Create( + nameof(MainPage), + typeof(ContentPage), + typeof(AccountSwitchingOverlayView), + defaultBindingMode: BindingMode.OneWay); + public static readonly BindableProperty MainFabProperty = BindableProperty.Create( nameof(MainFab), typeof(View), typeof(AccountSwitchingOverlayView), defaultBindingMode: BindingMode.OneWay); + public ContentPage MainPage + { + get => (ContentPage)GetValue(MainPageProperty); + set => SetValue(MainPageProperty, value); + } + public View MainFab { get => (View)GetValue(MainFabProperty); @@ -31,12 +43,26 @@ namespace Bit.App.Controls ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); + + SelectAccountCommand = new AsyncCommand(SelectAccountAsync, + onException: ex => _logger.Value.Exception(ex), + allowsMultipleExecutions: false); + + LongPressAccountCommand = new AsyncCommand(LongPressAccountAsync, + onException: ex => _logger.Value.Exception(ex), + allowsMultipleExecutions: false); } public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel; public ICommand ToggleVisibililtyCommand { get; } + public ICommand SelectAccountCommand { get; } + + public ICommand LongPressAccountCommand { get; } + + public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70; + public async Task ToggleVisibilityAsync() { if (IsVisible) @@ -51,13 +77,24 @@ namespace Bit.App.Controls public async Task ShowAsync() { - await ViewModel?.RefreshAccountViewsAsync(); + if (ViewModel == null) + { + return; + } + + await ViewModel.RefreshAccountViewsAsync(); await Device.InvokeOnMainThreadAsync(async () => { // start listView in default (off-screen) position await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0); + // re-measure in case accounts have been removed without changing screens + if (ViewModel.AccountViews != null) + { + _accountListView.HeightRequest = AccountListRowHeight * ViewModel.AccountViews.Count; + } + // set overlay opacity to zero before making visible and start fade-in Opacity = 0; IsVisible = true; @@ -113,16 +150,10 @@ namespace Bit.App.Controls } } - async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e) + private async Task SelectAccountAsync(AccountViewCellViewModel item) { try { - if (!(e.SelectedItem is AccountViewCellViewModel item)) - { - return; - } - - ((ListView)sender).SelectedItem = null; await Task.Delay(100); await HideAsync(); @@ -133,5 +164,25 @@ namespace Bit.App.Controls _logger.Value.Exception(ex); } } + + private async Task LongPressAccountAsync(AccountViewCellViewModel item) + { + if (!item.IsAccount) + { + return; + } + try + { + await Task.Delay(100); + await HideAsync(); + + ViewModel?.LongPressAccountCommand?.Execute( + new Tuple(MainPage, item)); + } + catch (Exception ex) + { + _logger.Value.Exception(ex); + } + } } } diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs index ed6f521ca..496496035 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using System.Windows.Input; +using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; @@ -24,6 +26,10 @@ namespace Bit.App.Controls SelectAccountCommand = new AsyncCommand(SelectAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); + + LongPressAccountCommand = new AsyncCommand>(LongPressAccountAsync, + onException: ex => logger.Exception(ex), + allowsMultipleExecutions: false); } // this needs to be a new list every time for the binding to get updated, @@ -37,6 +43,8 @@ namespace Bit.App.Controls public ICommand SelectAccountCommand { get; } + public ICommand LongPressAccountCommand { get; } + private async Task SelectAccountAsync(AccountViewCellViewModel item) { if (item.AccountView.IsAccount) @@ -57,6 +65,15 @@ namespace Bit.App.Controls } } + private async Task LongPressAccountAsync(Tuple item) + { + var (page, account) = item; + if (account.AccountView.IsAccount) + { + await AppHelpers.AccountListOptions(page, account); + } + } + public async Task RefreshAccountViewsAsync() { await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow); diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml b/src/App/Controls/AccountViewCell/AccountViewCell.xaml index 77773919a..c3eb578b7 100644 --- a/src/App/Controls/AccountViewCell/AccountViewCell.xaml +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml @@ -5,9 +5,15 @@ x:Class="Bit.App.Controls.AccountViewCell" xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:u="clr-namespace:Bit.App.Utilities" + x:Name="_accountView" x:DataType="controls:AccountViewCellViewModel"> + ColumnSpacing="0" + xct:TouchEffect.NativeAnimation="True" + xct:TouchEffect.Command="{Binding SelectAccountCommand, Source={x:Reference _accountView}}" + xct:TouchEffect.CommandParameter="{Binding .}" + xct:TouchEffect.LongPressCommand="{Binding LongPressAccountCommand, Source={x:Reference _accountView}}" + xct:TouchEffect.LongPressCommandParameter="{Binding .}"> @@ -71,7 +77,7 @@ - Remove Account + Hesabı sil - Are you sure you want to remove this account? + Bu istifadəçini silmək istədiyinizə əminsiniz? - Account Already Added + Hesab artıq əlavə edildi - Would you like to switch to it now? + Buna indi keçmək istəyirsiniz? Ana parol @@ -2105,19 +2105,28 @@ Bir və ya daha çox təşkilat siyasəti, fərdi anbarınızı ixrac etməyinizin qarşısını alır. - Add Account + Hesab əlavə et - Unlocked + Kilidi açıldı - Locked + Kilidli - Logged Out + Çıxış edildi - Switched to next available account + Növbəti mövcud hesaba keçildi + + + Hesab kilidlidir + + + Hesabdan uğurla çıxış edildi + + + Hesab uğurla silindi Hesabı sil diff --git a/src/App/Resources/AppResources.be.resx b/src/App/Resources/AppResources.be.resx index b59df1903..8883b51c3 100644 --- a/src/App/Resources/AppResources.be.resx +++ b/src/App/Resources/AppResources.be.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.bg.resx b/src/App/Resources/AppResources.bg.resx index 556291e62..3bacc6460 100644 --- a/src/App/Resources/AppResources.bg.resx +++ b/src/App/Resources/AppResources.bg.resx @@ -2120,6 +2120,15 @@ Превключено към следващата налична регистрация + + Регистрацията е заключена + + + Отписването от регистрацията беше успешно + + + Регистрацията беше премахната успешно + Изтриване на регистрацията diff --git a/src/App/Resources/AppResources.bn.resx b/src/App/Resources/AppResources.bn.resx index 5f2916b24..8e35ac480 100644 --- a/src/App/Resources/AppResources.bn.resx +++ b/src/App/Resources/AppResources.bn.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.bs.resx b/src/App/Resources/AppResources.bs.resx index eeabd2f6c..961bd56b1 100644 --- a/src/App/Resources/AppResources.bs.resx +++ b/src/App/Resources/AppResources.bs.resx @@ -2119,6 +2119,15 @@ Prebačeni ste na sljedeći dostupan račun + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Obriši račun diff --git a/src/App/Resources/AppResources.ca.resx b/src/App/Resources/AppResources.ca.resx index 864a99fbd..1ab027e72 100644 --- a/src/App/Resources/AppResources.ca.resx +++ b/src/App/Resources/AppResources.ca.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Suprimeix el compte diff --git a/src/App/Resources/AppResources.cs.resx b/src/App/Resources/AppResources.cs.resx index 64340bcb4..5cc2b11d3 100644 --- a/src/App/Resources/AppResources.cs.resx +++ b/src/App/Resources/AppResources.cs.resx @@ -2119,6 +2119,15 @@ Přepnuto na další dostupný účet + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Odstranit účet diff --git a/src/App/Resources/AppResources.da.resx b/src/App/Resources/AppResources.da.resx index 92276f0b1..62fbe7124 100644 --- a/src/App/Resources/AppResources.da.resx +++ b/src/App/Resources/AppResources.da.resx @@ -2119,6 +2119,15 @@ Skiftede til næste tilgængelige konto + + Konto låst + + + Konto logget ud + + + Konto fjernet + Slet konto diff --git a/src/App/Resources/AppResources.de.resx b/src/App/Resources/AppResources.de.resx index a4e9cf463..9daead8a3 100644 --- a/src/App/Resources/AppResources.de.resx +++ b/src/App/Resources/AppResources.de.resx @@ -2119,6 +2119,15 @@ Zum nächsten verfügbaren Konto gewechselt + + Konto gesperrt + + + Konto erfolgreich abgemeldet + + + Konto erfolgreich gelöscht + Konto löschen diff --git a/src/App/Resources/AppResources.el.resx b/src/App/Resources/AppResources.el.resx index e2a633a14..b891cbb83 100644 --- a/src/App/Resources/AppResources.el.resx +++ b/src/App/Resources/AppResources.el.resx @@ -2120,6 +2120,15 @@ Μετάβαση στον επόμενο διαθέσιμο λογαριασμό + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Διαγραφή Λογαριασμού diff --git a/src/App/Resources/AppResources.en-IN.resx b/src/App/Resources/AppResources.en-IN.resx index 7f9a9735f..d388d7f4a 100644 --- a/src/App/Resources/AppResources.en-IN.resx +++ b/src/App/Resources/AppResources.en-IN.resx @@ -2132,6 +2132,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.es.resx b/src/App/Resources/AppResources.es.resx index 882fc665a..3bd88ff26 100644 --- a/src/App/Resources/AppResources.es.resx +++ b/src/App/Resources/AppResources.es.resx @@ -2119,6 +2119,15 @@ Cambiado a la siguiente cuenta disponible + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Eliminar cuenta diff --git a/src/App/Resources/AppResources.et.resx b/src/App/Resources/AppResources.et.resx index 911d94786..3d47f34d3 100644 --- a/src/App/Resources/AppResources.et.resx +++ b/src/App/Resources/AppResources.et.resx @@ -2119,6 +2119,15 @@ Vahetati järgmise saadaoleva konto peale + + Kasutajakonto on lukus + + + Konto on edukalt välja logitud + + + Konto on edukalt eemaldatud + Kustuta konto diff --git a/src/App/Resources/AppResources.fa.resx b/src/App/Resources/AppResources.fa.resx index 5dd3dffcc..24713ec5d 100644 --- a/src/App/Resources/AppResources.fa.resx +++ b/src/App/Resources/AppResources.fa.resx @@ -2120,6 +2120,15 @@ به حساب بعدی موجود تغییر کرد + + حساب قفل شده است + + + حساب کاربری با موفقیت خارج شد + + + حساب کاربری با موفقیت حذف شد + حذف حساب diff --git a/src/App/Resources/AppResources.fi.resx b/src/App/Resources/AppResources.fi.resx index aef681084..cec123b03 100644 --- a/src/App/Resources/AppResources.fi.resx +++ b/src/App/Resources/AppResources.fi.resx @@ -2119,6 +2119,15 @@ Vaihdettu seuraavaan käytettävissä olevaan tiliin + + Tili lukittu + + + Tilin uloskirjaus onnistui + + + Tilin poisto onnistui + Poista tili diff --git a/src/App/Resources/AppResources.fil.resx b/src/App/Resources/AppResources.fil.resx index 5aa4fbda4..722a1b4df 100644 --- a/src/App/Resources/AppResources.fil.resx +++ b/src/App/Resources/AppResources.fil.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.fr.resx b/src/App/Resources/AppResources.fr.resx index cd4a98995..2b0de5b22 100644 --- a/src/App/Resources/AppResources.fr.resx +++ b/src/App/Resources/AppResources.fr.resx @@ -2119,6 +2119,15 @@ Passage au prochain compte disponible + + Compte verrouillé + + + Account logged out successfully + + + Account removed successfully + Supprimer le compte diff --git a/src/App/Resources/AppResources.he.resx b/src/App/Resources/AppResources.he.resx index 5e7a0b3b9..398589077 100644 --- a/src/App/Resources/AppResources.he.resx +++ b/src/App/Resources/AppResources.he.resx @@ -2126,6 +2126,15 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.hi.resx b/src/App/Resources/AppResources.hi.resx index bec536a81..3b6f21ef9 100644 --- a/src/App/Resources/AppResources.hi.resx +++ b/src/App/Resources/AppResources.hi.resx @@ -2121,6 +2121,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.hr.resx b/src/App/Resources/AppResources.hr.resx index 553dca1bb..4086a21e4 100644 --- a/src/App/Resources/AppResources.hr.resx +++ b/src/App/Resources/AppResources.hr.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Obriši račun diff --git a/src/App/Resources/AppResources.hu.resx b/src/App/Resources/AppResources.hu.resx index c62c4088c..6179a83d3 100644 --- a/src/App/Resources/AppResources.hu.resx +++ b/src/App/Resources/AppResources.hu.resx @@ -2119,6 +2119,15 @@ Megtörtént az átkapcsolás a következő elérhető fiókra. + + A fiók lezárásra került. + + + A fiókból kijelentkezés sikeres volt. + + + A fiók eltávolítása sikeres volt. + Fiók törlése diff --git a/src/App/Resources/AppResources.id.resx b/src/App/Resources/AppResources.id.resx index fc686da12..21a21589d 100644 --- a/src/App/Resources/AppResources.id.resx +++ b/src/App/Resources/AppResources.id.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.it.resx b/src/App/Resources/AppResources.it.resx index d5d27b072..2642b4392 100644 --- a/src/App/Resources/AppResources.it.resx +++ b/src/App/Resources/AppResources.it.resx @@ -196,7 +196,7 @@ Inviaci un'email per ottenere aiuto o fare una segnalazione. - Inserisci il tuo PIN. + Digita il tuo PIN. Preferiti @@ -247,7 +247,7 @@ Description message for the alert when internet connection is required to continue. - Connessione ad Internet richiesta + Connessione a Internet richiesta Title for the alert when internet connection is required to continue. @@ -378,7 +378,7 @@ Verifica impronta - Verifica Password Principale + Verifica password principale Verifica PIN @@ -393,7 +393,7 @@ Visita il nostro sito - Visita il nostro sito per ottenere aiuto, notizie, mandarci una email e/o imparare di più su come usare Bitwarden. + Visita il nostro sito per ottenere aiuto, notizie, mandarci un'email e/o imparare di più su come usare Bitwarden. Sito web @@ -430,7 +430,7 @@ Il modo più semplice per aggiungere nuovi login alla tua cassaforte è dall'estensione dell'applicazione Bitwarden. Scopri di più sull'utilizzo dell'estensione dell'applicazione Bitwarden navigando nella schermata "Impostazioni". - Usa Bitwarden su Safari e altre app per auto-completare i tuoi login. + Usa Bitwarden su Safari e altre applicazioni per auto-completare i tuoi login. Servizio auto-completamento di Bitwarden @@ -448,7 +448,7 @@ Cambia password principale - Puoi cambiare la tua password principale solo sulla cassaforte online di bitwarden.com. Vuoi visitare ora il sito? + Puoi cambiare la tua password principale solo sulla cassaforte in linea di bitwarden.com. Vuoi visitare ora il sito? Chiudi @@ -467,7 +467,7 @@ Modifica elemento - Abilita la sincronizzazione automatica + Abilita sincronizzazione automatica Inserisci l'indirizzo email del tuo account per ricevere il suggerimento della password principale. @@ -479,7 +479,7 @@ Quasi fatto! - Abilita l'Estensione App + Abilita l'estensione dell'applicazione Su Safari, trova Bitwarden utilizzando l'icona di condivisione (suggerimento: scorri a destra sulla riga inferiore del menu). @@ -489,7 +489,7 @@ Ottieni accesso immediato alle tue password! - Sei pronto a fare il login! + Sei pronto ad accedere! I tuoi login sono ora facilmente accessibili da Safari, Chrome e altre applicazioni supportate. @@ -510,10 +510,10 @@ Impronta - Genera Password + Genera password - Ottenere il suggerimento per la password principale + Ottieni il suggerimento per la password principale Importa elementi @@ -555,7 +555,7 @@ Azione timeout cassaforte - La disconnessione rimuove tutti gli accessi alla tua cassaforte e richiede l'autenticazione online dopo il periodo di scadenza. Sei sicuro di voler utilizzare questa impostazione? + La disconnessione rimuove tutti gli accessi alla tua cassaforte e richiede l'autenticazione in linea dopo il periodo di scadenza. Sei sicuro di voler utilizzare questa impostazione? Connessione in corso... @@ -574,10 +574,10 @@ La password principale è la password che utilizzi per accedere alla tua cassaforte. È molto importante che tu non la dimentichi. Non c'è modo di recuperare questa password nel caso che tu la dimenticassi. - Indizio per la Password Principale (opzionale) + Suggerimento per la password principale (facoltativo) - Un indizio per la password principale può aiutarti a ricordarla nel caso te la dimenticassi. + Un suggerimento per la password principale può aiutarti a ricordarla nel caso la dimenticassi. La password principale deve essere lunga almeno 8 caratteri. @@ -594,7 +594,7 @@ Altre impostazioni - È necessario accedere all'app di Bitwarden principale prima di poter utilizzare l'estensione. + È necessario accedere all'applicazione principale di Bitwarden prima di poter utilizzare l'estensione. Mai @@ -619,7 +619,7 @@ Confirmation, like "Ok, I understand it" - Le opzioni predefinite sono impostate dal generatore di password dell'app principale Bitwarden. + Le opzioni predefinite sono impostate dal generatore di password dell'applicazione principale Bitwarden. Opzioni @@ -631,7 +631,7 @@ Password generata. - Generatore di Password + Generatore di password Suggerimento password @@ -656,7 +656,7 @@ Rigenera password - Ri-digita la tua password principale + Ri-digita la password principale Cerca nella cassaforte @@ -671,7 +671,7 @@ Imposta PIN - Immetti un codice PIN a 4 cifre per sbloccare l'applicazione. + Digita un codice PIN a 4 cifre per sbloccare l'applicazione. Informazioni elemento @@ -704,7 +704,7 @@ Verifica in due passaggi - L'autenticazione in due passaggi rende il tuo account più sicuro, richiedendo di verificare l'accesso con un altro dispositivo come una chiave di sicurezza, app di autenticazione, SMS, telefonata o email. Può essere abilitata su bitwarden.com. Vuoi visitare il sito ora? + L'autenticazione in due passaggi rende il tuo account più sicuro, richiedendo di verificare l'accesso con un altro dispositivo come una chiave di sicurezza, applicazione di autenticazione, SMS, telefonata o email. Può essere abilitata su bitwarden.com. Vuoi visitare il sito ora? Sblocca con {0} @@ -759,7 +759,7 @@ Apri impostazioni di accessibilità - 1. Nella schermata impostazioni di accessibilità di Android, tocca "Bitwarden". + 1. Nella schermata impostazioni di accessibilità di Android, tocca "Bitwarden" nella gestione dei servizi. 2. Attiva l'interruttore e premi OK per accettare. @@ -1597,7 +1597,7 @@ Verifica biometrica - Dati biometrici + dati biometrici Sblocca con dati biometrici @@ -1665,7 +1665,7 @@ Clone an entity (verb). - Una o più policy dell'organizzazione controllano le impostazioni del tuo generatore + Una o più politiche dell'organizzazione stanno influenzando le impostazioni del tuo generatore Apri @@ -1926,7 +1926,7 @@ Sei sicuro di voler rimuovere la password? - Rimuovo la password + Rimozione password La password è stata rimossa. @@ -2052,10 +2052,10 @@ Aggiorna password principale - La tua password principale è stata recentemente modificata da un amministratore nella tua organizzazione. Per accedere alla cassaforte, devi aggiornarla ora. Procedendo sarai disconnesso dalla sessione attuale, richiedendo di effettuare nuovamente l'accesso. Le sessioni attive su altri dispositivi possono continuare a rimanere attive per un massimo di un'ora. + La tua password principale è stata recentemente modificata da un amministratore nella tua organizzazione. Per accedere alla cassaforte, aggiorna ora la password principale. Procedendo sarai disconnesso dalla sessione attuale e ti sarà richiesto di effettuare nuovamente l'accesso. Le sessioni attive su altri dispositivi potrebbero continuare a rimanere attive per un massimo di un'ora. - Aggiornamento password in corso + Aggiornamento password Attualmente non è possibile aggiornare la password @@ -2094,7 +2094,7 @@ Assicurati che il browser predefinito supporti WebAuthn e riprova. - Questa organizzazione ha una policy aziendale che ti iscriverà automaticamente al ripristino della password. Ciò permetterà agli amministratori dell'organizzazione di cambiare la tua password principale. + Questa organizzazione ha una politica aziendale che ti iscriverà automaticamente al ripristino della password. Ciò permetterà agli amministratori dell'organizzazione di cambiare la tua password principale. Le policy dell'organizzazione controllano il timeout della tua cassaforte. Il tempo massimo consentito è di $HOURS$ ore e $MINUTES$ minuti @@ -2120,6 +2120,15 @@ Passato all'account successivo disponibile + + Account bloccato + + + Account disconnesso correttamente + + + Account rimosso correttamente + Elimina account diff --git a/src/App/Resources/AppResources.ja.resx b/src/App/Resources/AppResources.ja.resx index 9f7553904..61a75b71d 100644 --- a/src/App/Resources/AppResources.ja.resx +++ b/src/App/Resources/AppResources.ja.resx @@ -2119,6 +2119,15 @@ 次の利用可能なアカウントに切り替えました + + アカウントをロックしました + + + ログアウトしました + + + アカウントを削除しました + アカウントの削除 diff --git a/src/App/Resources/AppResources.ka.resx b/src/App/Resources/AppResources.ka.resx index 5aa4fbda4..722a1b4df 100644 --- a/src/App/Resources/AppResources.ka.resx +++ b/src/App/Resources/AppResources.ka.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.kn.resx b/src/App/Resources/AppResources.kn.resx index 21950bde7..3c2dd80fe 100644 --- a/src/App/Resources/AppResources.kn.resx +++ b/src/App/Resources/AppResources.kn.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.ko.resx b/src/App/Resources/AppResources.ko.resx index b61008092..d30696d7a 100644 --- a/src/App/Resources/AppResources.ko.resx +++ b/src/App/Resources/AppResources.ko.resx @@ -2119,6 +2119,15 @@ 사용 가능한 다음 계정으로 전환함 + + Account Locked + + + Account logged out successfully + + + Account removed successfully + 계정 삭제 diff --git a/src/App/Resources/AppResources.lv.resx b/src/App/Resources/AppResources.lv.resx index 9585ba72f..7cd73cd2d 100644 --- a/src/App/Resources/AppResources.lv.resx +++ b/src/App/Resources/AppResources.lv.resx @@ -2119,6 +2119,15 @@ Pārslēdzās uz nākamo pieejamo kontu + + Konts ir slēgts + + + Izrakstīšanās no konta bija veiksmīga + + + Konts tika veiksmīgi noņemts + Dzēst kontu diff --git a/src/App/Resources/AppResources.ml.resx b/src/App/Resources/AppResources.ml.resx index 95fc739fc..a205a062e 100644 --- a/src/App/Resources/AppResources.ml.resx +++ b/src/App/Resources/AppResources.ml.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.nb.resx b/src/App/Resources/AppResources.nb.resx index e2ba12b81..1ae6eae4c 100644 --- a/src/App/Resources/AppResources.nb.resx +++ b/src/App/Resources/AppResources.nb.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Slett konto diff --git a/src/App/Resources/AppResources.nl.resx b/src/App/Resources/AppResources.nl.resx index 0ea55b20a..0325ec3c2 100644 --- a/src/App/Resources/AppResources.nl.resx +++ b/src/App/Resources/AppResources.nl.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account geblokkeerd + + + Account succesvol uitgelogd + + + Account succesvol verwijderd + Account verwijderen diff --git a/src/App/Resources/AppResources.nn.resx b/src/App/Resources/AppResources.nn.resx index 5aa4fbda4..722a1b4df 100644 --- a/src/App/Resources/AppResources.nn.resx +++ b/src/App/Resources/AppResources.nn.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.pl.resx b/src/App/Resources/AppResources.pl.resx index bf566bb31..8795a97da 100644 --- a/src/App/Resources/AppResources.pl.resx +++ b/src/App/Resources/AppResources.pl.resx @@ -2119,6 +2119,15 @@ Przełączono na następne dostępne konto + + Konto zostało zablokowane + + + Wylogowano z konta + + + Konto zostało usunięte + Usuń konto diff --git a/src/App/Resources/AppResources.pt-BR.resx b/src/App/Resources/AppResources.pt-BR.resx index 70b1bcabb..fd10d3f4c 100644 --- a/src/App/Resources/AppResources.pt-BR.resx +++ b/src/App/Resources/AppResources.pt-BR.resx @@ -1162,7 +1162,7 @@ Serviço de Acessibilidade de Autopreenchimento - O serviço de autopreenchimento do Bitwarden utiliza a Estrutura de Autopreenchimento do Android para ajudar no preenchimento de credenciais, cartões de crédito, e informação de identidade em outros aplicativos do seu dispositivo. + O serviço de autopreenchimento do Bitwarden usa a Estrutura de Preenchimento Automático do Android para auxiliar no preenchimento de credenciais em outros aplicativos no seu dispositivo. Use o serviço de acessibilidade do bitwarden para preencher automaticamente suas credenciais. @@ -2120,6 +2120,15 @@ Alterada para a próxima conta disponível + + Conta Bloqueada + + + Conta desconectada com sucesso + + + Conta removida com sucesso + Excluir Conta diff --git a/src/App/Resources/AppResources.ro.resx b/src/App/Resources/AppResources.ro.resx index 7db363c12..a213137b0 100644 --- a/src/App/Resources/AppResources.ro.resx +++ b/src/App/Resources/AppResources.ro.resx @@ -2119,6 +2119,15 @@ Comutat la următorul cont disponibil + + Cont blocat + + + Contul s-a deconectat cu succes + + + Cont eliminat cu succes + Ștergere cont diff --git a/src/App/Resources/AppResources.ru.resx b/src/App/Resources/AppResources.ru.resx index cce2a4f78..8f7766f46 100644 --- a/src/App/Resources/AppResources.ru.resx +++ b/src/App/Resources/AppResources.ru.resx @@ -2119,6 +2119,15 @@ Переключено на следующую доступную учетную запись + + Учетная запись заблокирована + + + Вы успешно вышли из учетной записи + + + Учетная запись успешно удалена + Удалить аккаунт diff --git a/src/App/Resources/AppResources.si.resx b/src/App/Resources/AppResources.si.resx index 775e41c24..91b4a2b42 100644 --- a/src/App/Resources/AppResources.si.resx +++ b/src/App/Resources/AppResources.si.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.sk.resx b/src/App/Resources/AppResources.sk.resx index b210a279b..b18b8430e 100644 --- a/src/App/Resources/AppResources.sk.resx +++ b/src/App/Resources/AppResources.sk.resx @@ -2119,6 +2119,15 @@ Prepnuté na ďalší dostupný účet + + Zamknutý účet + + + Účet bol úspešne odhlásený + + + Účet bol úspešne odstránený + Odstrániť účet diff --git a/src/App/Resources/AppResources.sl.resx b/src/App/Resources/AppResources.sl.resx index b4f3d525e..0d8c6bf92 100644 --- a/src/App/Resources/AppResources.sl.resx +++ b/src/App/Resources/AppResources.sl.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Izbriši račun diff --git a/src/App/Resources/AppResources.sr.resx b/src/App/Resources/AppResources.sr.resx index 941c0dba2..d186a19d3 100644 --- a/src/App/Resources/AppResources.sr.resx +++ b/src/App/Resources/AppResources.sr.resx @@ -2121,6 +2121,15 @@ Пребацили сте се на следећи доступни налог + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Избриши Налог diff --git a/src/App/Resources/AppResources.sv.resx b/src/App/Resources/AppResources.sv.resx index 652b30c7c..24454ee56 100644 --- a/src/App/Resources/AppResources.sv.resx +++ b/src/App/Resources/AppResources.sv.resx @@ -1162,10 +1162,10 @@ Tillgänglighetstjänst för automatisk ifyllnad - Bitwardens tillgänglighetstjänst för automatisk ifyllnad använder Android Autofill Framework för att assistera med att fylla i inloggningar, kreditkort och identitetsinformation inuti andra appar på din enhet. + The Bitwarden auto-fill service uses the Android Autofill Framework to assist in filling login information into other apps on your device. - Använd Bitwardens tillgänglighetstjänst för att automatiskt fylla i inloggningar, kreditkort och identitetsinformation inuti andra appar. + Use the Bitwarden auto-fill service to fill login information into other apps. Öppna inställningar för automatisk ifyllnad @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Radera konto diff --git a/src/App/Resources/AppResources.ta.resx b/src/App/Resources/AppResources.ta.resx index 4a454605d..a4712a580 100644 --- a/src/App/Resources/AppResources.ta.resx +++ b/src/App/Resources/AppResources.ta.resx @@ -2120,6 +2120,15 @@ அடுத்து கிடைத்த கணக்கிற்கு நிலைமாறியது + + Account Locked + + + Account logged out successfully + + + Account removed successfully + கணக்கை அழி diff --git a/src/App/Resources/AppResources.th.resx b/src/App/Resources/AppResources.th.resx index 8d2c4698b..1a2a96b53 100644 --- a/src/App/Resources/AppResources.th.resx +++ b/src/App/Resources/AppResources.th.resx @@ -2120,6 +2120,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.tr.resx b/src/App/Resources/AppResources.tr.resx index 721563d19..fa71266d4 100644 --- a/src/App/Resources/AppResources.tr.resx +++ b/src/App/Resources/AppResources.tr.resx @@ -2119,6 +2119,15 @@ Bir sonraki hesaba geçildi + + Hesap kilitlendi + + + Başarıyla çıkış yapıldı + + + Hesap başarıyla kaldırıldı + Hesabı sil diff --git a/src/App/Resources/AppResources.uk.resx b/src/App/Resources/AppResources.uk.resx index 262393e3c..5629aa016 100644 --- a/src/App/Resources/AppResources.uk.resx +++ b/src/App/Resources/AppResources.uk.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Видалити обліковий запис diff --git a/src/App/Resources/AppResources.vi.resx b/src/App/Resources/AppResources.vi.resx index ed8c554c3..9cdf2917c 100644 --- a/src/App/Resources/AppResources.vi.resx +++ b/src/App/Resources/AppResources.vi.resx @@ -2119,6 +2119,15 @@ Switched to next available account + + Account Locked + + + Account logged out successfully + + + Account removed successfully + Delete Account diff --git a/src/App/Resources/AppResources.zh-Hant.resx b/src/App/Resources/AppResources.zh-Hant.resx index 741381699..0b4652f68 100644 --- a/src/App/Resources/AppResources.zh-Hant.resx +++ b/src/App/Resources/AppResources.zh-Hant.resx @@ -2119,6 +2119,15 @@ 已切換到下一個可用的帳戶 + + 帳戶已鎖定 + + + 帳戶已成功登出 + + + 帳戶已成功移除 + 刪除帳戶 From 4d4e246a4757b6a3550f9b492a0cd5bce58647ed Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Mon, 14 Mar 2022 13:49:57 -0300 Subject: [PATCH 048/100] Fix #1745 crash on scroll of grouped collection view on iOS 15.4 beta (#1842) --- .../Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml | 3 +-- src/App/Pages/Settings/SettingsPage/SettingsPage.xaml | 3 +-- .../Pages/Settings/SettingsPage/SettingsPageViewModel.cs | 9 ++++++--- src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml | 3 +-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml index 2c6905d25..114feede1 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml @@ -125,8 +125,7 @@ Spacing="0" Padding="0" VerticalOptions="FillAndExpand" StyleClass="list-row-header-container, list-row-header-container-platform"> + StyleClass="list-section-separator-top, list-section-separator-top-platform" /> - Remove Account + Suprimeix el compte - Are you sure you want to remove this account? + Voleu suprimir el compte? - Account Already Added + El compte ja s'ha afegit - Would you like to switch to it now? + T'agradaria canviar-lo ara? Contrasenya mestra @@ -2105,28 +2105,28 @@ Una o més polítiques d'organització us impedeixen exportar la vostra caixa forta. - Add Account + Afig compte - Unlocked + Desbloquejat - Locked + Bloquejat - Logged Out + Sessió tancada - Switched to next available account + S'ha canviat al següent compte disponible - Account Locked + Compte bloquejat - Account logged out successfully + El compte s'ha tancat correctament - Account removed successfully + El compte s'ha suprimit correctament Suprimeix el compte diff --git a/src/App/Resources/AppResources.fi.resx b/src/App/Resources/AppResources.fi.resx index cec123b03..e80d567a0 100644 --- a/src/App/Resources/AppResources.fi.resx +++ b/src/App/Resources/AppResources.fi.resx @@ -1538,7 +1538,7 @@ Oletus (järjestelmä) - Kopioi muistiinpanot + Kopioi merkinnät Poistu @@ -1569,7 +1569,7 @@ Kun sovellus käynnistetään uudelleen - Automaattinen täyttö tekee Bitwarden-holvisi käytöstä sivustoilla ja muissa sovelluksissa helppoa. Näyttää siltä, ettei automaattista täyttöä ole otettu käyttöön. Voit tehdä sen "Asetukset" -näytöstä. + Automaattinen täyttö tekee Bitwarden-holvisi käytöstä sivustoilla ja muissa sovelluksissa helppoa. Näyttää siltä, ettei automaattista täyttöä ole otettu käyttöön. Voit tehdä sen "Asetukset" -ruudusta. Teema vaihtuu kun sovellus käynnistetään uudelleen. @@ -1931,7 +1931,7 @@ Salasana on poistettu. - Yksityiset muistiinpanot tästä Sendistä. + Yksityisiä merkintöjä tästä Sendistä. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. diff --git a/src/App/Resources/AppResources.fr.resx b/src/App/Resources/AppResources.fr.resx index 2b0de5b22..46102dd87 100644 --- a/src/App/Resources/AppResources.fr.resx +++ b/src/App/Resources/AppResources.fr.resx @@ -1165,7 +1165,7 @@ Le service de saisie automatique de Bitwarden utilise l'outil de saisie automatique d'Android pour aider à saisir les identifiants, les cartes de crédit et les informations d'identité dans d'autres applis sur votre appareil. - Utilisez le service d'accessibilité de Bitwarden pour la saisie automatique de vos identifiants. + Utilisez le service de remplissage automatique de Bitwarden pour remplir les informations de connexion dans d'autres applications. Ouvrir les paramètres de remplissage automatique @@ -1935,7 +1935,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Désactiver ce Send pour que personne ne puisse y accéder. + Désactiver cet envoi pour que personne ne puisse y accéder. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2123,10 +2123,10 @@ Compte verrouillé - Account logged out successfully + Compte déconnecté avec succès - Account removed successfully + Compte supprimé avec succès Supprimer le compte diff --git a/src/App/Resources/AppResources.nb.resx b/src/App/Resources/AppResources.nb.resx index 1ae6eae4c..614dbac0a 100644 --- a/src/App/Resources/AppResources.nb.resx +++ b/src/App/Resources/AppResources.nb.resx @@ -276,13 +276,13 @@ Er du sikker på at du vil logge av? - Remove Account + Fjern konto - Are you sure you want to remove this account? + Er du sikker på at du vil fjerne denne kontoen? - Account Already Added + Kontoen er allerede lagt til Would you like to switch to it now? @@ -2106,13 +2106,13 @@ En eller flere organisasjonsoppsettsregler hindrer deg i å eksportere ditt personlige hvelv. - Add Account + Legg til konto - Unlocked + Ulåst - Locked + Låst Logged Out From 383eee6ec7d40f2c7314f68a4560d1800a521dba Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Fri, 18 Mar 2022 15:41:15 -0300 Subject: [PATCH 056/100] Fixed flickering on iOS while loading collections for the collection crash hotfix (#1852) --- .../SendGroupingsPageViewModel.cs | 32 ++++++++++++---- .../SettingsPage/SettingsPageViewModel.cs | 37 +++++++++++++------ .../Vault/AutofillCiphersPageViewModel.cs | 32 ++++++++++++---- .../GroupingsPage/GroupingsPageViewModel.cs | 32 ++++++++++++---- 4 files changed, 97 insertions(+), 36 deletions(-) diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs index 66f207211..4c7e70c37 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs @@ -178,7 +178,9 @@ namespace Bit.App.Pages } // TODO: refactor this - if (Device.RuntimePlatform == Device.Android) + if (Device.RuntimePlatform == Device.Android + || + GroupedSends.Any()) { var items = new List(); foreach (var itemGroup in groupedSends) @@ -191,16 +193,30 @@ namespace Bit.App.Pages } else { - // HACK: This waitings are to avoid crash on iOS - GroupedSends.Clear(); - await Task.Delay(60); - + // HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list + var first = true; + var items = new List(); foreach (var itemGroup in groupedSends) { - GroupedSends.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); - await Task.Delay(60); + if (!first) + { + items.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + } + else + { + first = false; + } + items.AddRange(itemGroup); + } - GroupedSends.AddRange(itemGroup); + if (groupedSends.Any()) + { + GroupedSends.ReplaceRange(new List { new SendGroupingsPageHeaderListItem(groupedSends[0].Name, groupedSends[0].ItemCount) }); + GroupedSends.AddRange(items); + } + else + { + GroupedSends.Clear(); } } } diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 0af8a2ea6..48b67bf1f 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -515,7 +515,9 @@ namespace Bit.App.Pages }; // TODO: refactor this - if (Device.RuntimePlatform == Device.Android) + if (Device.RuntimePlatform == Device.Android + || + GroupedItems.Any()) { var items = new List(); foreach (var itemGroup in settingsListGroupItems) @@ -528,20 +530,31 @@ namespace Bit.App.Pages } else { - Device.InvokeOnMainThreadAsync(async () => + // HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list + var first = true; + var items = new List(); + foreach (var itemGroup in settingsListGroupItems) { - // HACK: This waitings are to avoid crash on iOS - GroupedItems.Clear(); - await Task.Delay(60); - - foreach (var itemGroup in settingsListGroupItems) + if (!first) { - GroupedItems.Add(new SettingsPageHeaderListItem(itemGroup.Name)); - await Task.Delay(60); - - GroupedItems.AddRange(itemGroup); + items.Add(new SettingsPageHeaderListItem(itemGroup.Name)); } - }).FireAndForget(); + else + { + first = false; + } + items.AddRange(itemGroup); + } + + if (settingsListGroupItems.Any()) + { + GroupedItems.ReplaceRange(new List { new SettingsPageHeaderListItem(settingsListGroupItems[0].Name) }); + GroupedItems.AddRange(items); + } + else + { + GroupedItems.Clear(); + } } } diff --git a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs index 5cad8f53e..b90a144a2 100644 --- a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs +++ b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs @@ -108,7 +108,9 @@ namespace Bit.App.Pages } // TODO: refactor this - if (Device.RuntimePlatform == Device.Android) + if (Device.RuntimePlatform == Device.Android + || + GroupedItems.Any()) { var items = new List(); foreach (var itemGroup in groupedItems) @@ -121,16 +123,30 @@ namespace Bit.App.Pages } else { - // HACK: This waitings are to avoid crash on iOS - GroupedItems.Clear(); - await Task.Delay(60); - + // HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list + var first = true; + var items = new List(); foreach (var itemGroup in groupedItems) { - GroupedItems.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); - await Task.Delay(60); + if (!first) + { + items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + } + else + { + first = false; + } + items.AddRange(itemGroup); + } - GroupedItems.AddRange(itemGroup); + if (groupedItems.Any()) + { + GroupedItems.ReplaceRange(new List { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) }); + GroupedItems.AddRange(items); + } + else + { + GroupedItems.Clear(); } } ShowList = groupedItems.Any(); diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 7ffe1f982..57ee29f52 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -283,7 +283,9 @@ namespace Bit.App.Pages } // TODO: refactor this - if (Device.RuntimePlatform == Device.Android) + if (Device.RuntimePlatform == Device.Android + || + GroupedItems.Any()) { var items = new List(); foreach (var itemGroup in groupedItems) @@ -296,16 +298,30 @@ namespace Bit.App.Pages } else { - // HACK: This waitings are to avoid crash on iOS - GroupedItems.Clear(); - await Task.Delay(60); - + // HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list + var first = true; + var items = new List(); foreach (var itemGroup in groupedItems) { - GroupedItems.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); - await Task.Delay(60); + if (!first) + { + items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + } + else + { + first = false; + } + items.AddRange(itemGroup); + } - GroupedItems.AddRange(itemGroup); + if (groupedItems.Any()) + { + GroupedItems.ReplaceRange(new List { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) }); + GroupedItems.AddRange(items); + } + else + { + GroupedItems.Clear(); } } } From 4734fe4e43cd4e6dbbeaf1528d10331ee57ebccb Mon Sep 17 00:00:00 2001 From: jnolan912 <32627910+jnolan912@users.noreply.github.com> Date: Fri, 18 Mar 2022 16:00:37 -0400 Subject: [PATCH 057/100] Update ios Autofill page to match setting's name (#1354) The option in the ios menu to get to autofill settings is called "Passwords" instead of "Passwords & Accounts" --- src/App/Resources/AppResources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index f6aa5e77c..bd7f76cee 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" From fdcb2d76c9b03ca75b2274d5276acdd98a18b616 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Mar 2022 13:21:46 -0700 Subject: [PATCH 058/100] Bumped version to 2.16.5 (#1854) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Android/Properties/AndroidManifest.xml | 2 +- src/iOS.Autofill/Info.plist | 2 +- src/iOS.Extension/Info.plist | 2 +- src/iOS.ShareExtension/Info.plist | 2 +- src/iOS/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index cb19e56c8..dad296d25 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index 2b63f4832..c32540b97 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2.16.4 + 2.16.5 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index 3ed66a580..313c71b88 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2.16.4 + 2.16.5 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index 4e1250a35..42df67d28 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2.16.4 + 2.16.5 CFBundleVersion 1 MinimumOSVersion diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index 371e1b155..60ce51893 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2.16.4 + 2.16.5 CFBundleVersion 1 CFBundleIconName From 840925c4792c917f56775cd1ee08b6eb283ee1a7 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Mon, 21 Mar 2022 12:34:22 -0300 Subject: [PATCH 059/100] Added null checks for iOS crash OnActivated on KeyWindow (#1856) --- src/iOS/AppDelegate.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 0b213e832..ad46ba1cf 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -24,6 +24,8 @@ namespace Bit.iOS [Register("AppDelegate")] public partial class AppDelegate : FormsApplicationDelegate { + const int SPLASH_VIEW_TAG = 4321; + private NFCNdefReaderSession _nfcSession = null; private iOSPushNotificationHandler _pushHandler = null; private Core.NFCReaderDelegate _nfcDelegate = null; @@ -175,7 +177,7 @@ namespace Bit.iOS { var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame) { - Tag = 4321 + Tag = SPLASH_VIEW_TAG }; var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame) { @@ -205,11 +207,9 @@ namespace Bit.iOS { base.OnActivated(uiApplication); UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0; - var view = UIApplication.SharedApplication.KeyWindow.ViewWithTag(4321); - if (view != null) - { - view.RemoveFromSuperview(); - } + UIApplication.SharedApplication.KeyWindow? + .ViewWithTag(SPLASH_VIEW_TAG)? + .RemoveFromSuperview(); ThemeManager.UpdateThemeOnPagesAsync(); } From f10307c72d28c1afc3318ce45787971063d49a1c Mon Sep 17 00:00:00 2001 From: Matt Portune <59324545+mportune-bw@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:44:54 -0400 Subject: [PATCH 060/100] Remove verbose state & value storage debug logging (#1857) --- src/Core/Services/StateService.cs | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 131ddb7d4..f26df9457 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1,7 +1,6 @@ using System; using Bit.Core.Abstractions; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Bit.Core.Enums; @@ -9,7 +8,6 @@ using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -1183,20 +1181,16 @@ namespace Bit.Core.Services private async Task GetValueAsync(string key, StorageOptions options) { - var value = await GetStorageService(options).GetAsync(key); - Log("GET", options, key, JsonConvert.SerializeObject(value)); - return value; + return await GetStorageService(options).GetAsync(key); } private async Task SetValueAsync(string key, T value, StorageOptions options) { if (value == null) { - Log("REMOVE", options, key, null); await GetStorageService(options).RemoveAsync(key); return; } - Log("SET", options, key, JsonConvert.SerializeObject(value)); await GetStorageService(options).SaveAsync(key, value); } @@ -1512,19 +1506,12 @@ namespace Bit.Core.Services private async Task GetStateFromStorageAsync() { - var state = await _storageService.GetAsync(Constants.StateKey); - // TODO Remove logging once all bugs are squished - Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), - ">>> GetStateFromStorageAsync()"); - return state; + return await _storageService.GetAsync(Constants.StateKey); } private async Task SaveStateToStorageAsync(State state) { await _storageService.SaveAsync(Constants.StateKey, state); - // TODO Remove logging once all bugs are squished - Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented), - ">>> SaveStateToStorageAsync()"); } private async Task CheckStateAsync() @@ -1567,17 +1554,5 @@ namespace Bit.Core.Services } throw new Exception("User does not exist in account list"); } - - private void Log(string tag, StorageOptions options, string key, string value) - { - // TODO Remove this once all bugs are squished - var text = options?.UseSecureStorage ?? false ? "SECURE / " : ""; - text += "Key: " + key + " / "; - if (value != null) - { - text += "Value: " + value; - } - Debug.WriteLine(text, ">>> " + tag); - } } } From a3a508eb83803531799b1b086af49e9ed0800130 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 10:49:58 -0600 Subject: [PATCH 061/100] Bump version to 2.17.0 (#1858) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Android/Properties/AndroidManifest.xml | 2 +- src/iOS.Autofill/Info.plist | 2 +- src/iOS.Extension/Info.plist | 2 +- src/iOS.ShareExtension/Info.plist | 2 +- src/iOS/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index dad296d25..6a748d5a1 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index c32540b97..b0d13a052 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2.16.5 + 2.17.0 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index 313c71b88..ef7a1761a 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2.16.5 + 2.17.0 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index 42df67d28..d8e174834 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2.16.5 + 2.17.0 CFBundleVersion 1 MinimumOSVersion diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index 60ce51893..4a5d2917a 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2.16.5 + 2.17.0 CFBundleVersion 1 CFBundleIconName From 4bd06d23935870dcfb0b43dcbc288de8689e34eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 12:41:49 -0600 Subject: [PATCH 062/100] Bump version to 2.17.1 (#1859) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Android/Properties/AndroidManifest.xml | 2 +- src/iOS.Autofill/Info.plist | 2 +- src/iOS.Extension/Info.plist | 2 +- src/iOS.ShareExtension/Info.plist | 2 +- src/iOS/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index 6a748d5a1..5ae9ff0c4 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index b0d13a052..1eb983d45 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2.17.0 + 2.17.1 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index ef7a1761a..b192183aa 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2.17.0 + 2.17.1 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index d8e174834..a9d128948 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2.17.0 + 2.17.1 CFBundleVersion 1 MinimumOSVersion diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index 4a5d2917a..5baae1815 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2.17.0 + 2.17.1 CFBundleVersion 1 CFBundleIconName From 0796bf17ce42006e6218b69a9bd666273de46d2c Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Thu, 24 Mar 2022 15:53:12 -0400 Subject: [PATCH 063/100] Fix for missing token when checking for key connector migration (#1861) --- src/Core/Services/TokenService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/Services/TokenService.cs b/src/Core/Services/TokenService.cs index 817cf6ad4..c30d33479 100644 --- a/src/Core/Services/TokenService.cs +++ b/src/Core/Services/TokenService.cs @@ -209,6 +209,10 @@ namespace Bit.Core.Services { await GetTokenAsync(); } + if (_accessTokenForDecoding == null) + { + return false; + } var decoded = DecodeToken(); if (decoded?["amr"] == null) { From 284d7280231462b42f3b99c535c1bfd84c96a4b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 01:21:43 +0100 Subject: [PATCH 064/100] Autosync the updated translations (#1863) Co-authored-by: github-actions <> --- src/App/Resources/AppResources.bn.resx | 2 +- src/App/Resources/AppResources.bs.resx | 6 ++--- src/App/Resources/AppResources.de.resx | 2 +- src/App/Resources/AppResources.fil.resx | 2 +- src/App/Resources/AppResources.fr.resx | 2 +- src/App/Resources/AppResources.hi.resx | 2 +- src/App/Resources/AppResources.ka.resx | 2 +- src/App/Resources/AppResources.nb.resx | 2 +- src/App/Resources/AppResources.nn.resx | 2 +- src/App/Resources/AppResources.ro.resx | 2 +- src/App/Resources/AppResources.ru.resx | 2 +- src/App/Resources/AppResources.si.resx | 2 +- src/App/Resources/AppResources.sk.resx | 2 +- src/App/Resources/AppResources.sl.resx | 2 +- src/App/Resources/AppResources.sr.resx | 6 ++--- src/App/Resources/AppResources.ta.resx | 2 +- src/App/Resources/AppResources.th.resx | 2 +- src/App/Resources/AppResources.uk.resx | 34 ++++++++++++------------- 18 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/App/Resources/AppResources.bn.resx b/src/App/Resources/AppResources.bn.resx index 8e35ac480..15255c198 100644 --- a/src/App/Resources/AppResources.bn.resx +++ b/src/App/Resources/AppResources.bn.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.bs.resx b/src/App/Resources/AppResources.bs.resx index 961bd56b1..3a11f45f2 100644 --- a/src/App/Resources/AppResources.bs.resx +++ b/src/App/Resources/AppResources.bs.resx @@ -2120,13 +2120,13 @@ Prebačeni ste na sljedeći dostupan račun - Account Locked + Račun zaključan - Account logged out successfully + Uspješno odjavljeni sa računa - Account removed successfully + Račun je uspješno uklonjen Obriši račun diff --git a/src/App/Resources/AppResources.de.resx b/src/App/Resources/AppResources.de.resx index 9daead8a3..1544aa920 100644 --- a/src/App/Resources/AppResources.de.resx +++ b/src/App/Resources/AppResources.de.resx @@ -1299,7 +1299,7 @@ 1. Gehe in die iOS Einstellungen - 2. Drücke "Passwörter & Accounts" + 2. Tippe auf "Passwörter" 3. Tippe auf "Automatisch ausfüllen" diff --git a/src/App/Resources/AppResources.fil.resx b/src/App/Resources/AppResources.fil.resx index 722a1b4df..15372b1ba 100644 --- a/src/App/Resources/AppResources.fil.resx +++ b/src/App/Resources/AppResources.fil.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.fr.resx b/src/App/Resources/AppResources.fr.resx index 46102dd87..45ea31e66 100644 --- a/src/App/Resources/AppResources.fr.resx +++ b/src/App/Resources/AppResources.fr.resx @@ -1299,7 +1299,7 @@ 1. Allez dans l'application "Réglages" d'iOS - 2. Appuyez sur "Mots de passe et comptes" + 2. Appuyez sur "Mots de passes et comptes" 3. Appuyez sur "Préremplir mots de passe" diff --git a/src/App/Resources/AppResources.hi.resx b/src/App/Resources/AppResources.hi.resx index 3b6f21ef9..7f9c358e5 100644 --- a/src/App/Resources/AppResources.hi.resx +++ b/src/App/Resources/AppResources.hi.resx @@ -1300,7 +1300,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.ka.resx b/src/App/Resources/AppResources.ka.resx index 722a1b4df..15372b1ba 100644 --- a/src/App/Resources/AppResources.ka.resx +++ b/src/App/Resources/AppResources.ka.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.nb.resx b/src/App/Resources/AppResources.nb.resx index 614dbac0a..4a291a987 100644 --- a/src/App/Resources/AppResources.nb.resx +++ b/src/App/Resources/AppResources.nb.resx @@ -1299,7 +1299,7 @@ 1. Gå til iOS-appen «Innstillinger» - 2. Trykk på «Passord og kontoer» + 2. Trykk på "Passord og kontoer" 3. Trykk «Autoutfyll passord» diff --git a/src/App/Resources/AppResources.nn.resx b/src/App/Resources/AppResources.nn.resx index 722a1b4df..15372b1ba 100644 --- a/src/App/Resources/AppResources.nn.resx +++ b/src/App/Resources/AppResources.nn.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.ro.resx b/src/App/Resources/AppResources.ro.resx index a213137b0..3a019ce7d 100644 --- a/src/App/Resources/AppResources.ro.resx +++ b/src/App/Resources/AppResources.ro.resx @@ -1299,7 +1299,7 @@ 1. Accesați Setările iOS - 2. Selectați "Parole și Conturi" + 2. Selectați „Parole” 3. Selectați "Auto-completare parole" diff --git a/src/App/Resources/AppResources.ru.resx b/src/App/Resources/AppResources.ru.resx index 8f7766f46..851131357 100644 --- a/src/App/Resources/AppResources.ru.resx +++ b/src/App/Resources/AppResources.ru.resx @@ -1299,7 +1299,7 @@ 1. Перейдите в 'Настройки' iOS - 2. Нажмите 'Пароли и учетные записи' + 2. Нажмите на пункт "Пароли и учетные записи" 3. Нажмите 'Автозаполнение паролей' diff --git a/src/App/Resources/AppResources.si.resx b/src/App/Resources/AppResources.si.resx index 91b4a2b42..1628dd12e 100644 --- a/src/App/Resources/AppResources.si.resx +++ b/src/App/Resources/AppResources.si.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.sk.resx b/src/App/Resources/AppResources.sk.resx index b18b8430e..24e8daa08 100644 --- a/src/App/Resources/AppResources.sk.resx +++ b/src/App/Resources/AppResources.sk.resx @@ -1428,7 +1428,7 @@ Počet slov - Heslo + Prístupová fráza Oddeľovač slov diff --git a/src/App/Resources/AppResources.sl.resx b/src/App/Resources/AppResources.sl.resx index 0d8c6bf92..2e8ffa54a 100644 --- a/src/App/Resources/AppResources.sl.resx +++ b/src/App/Resources/AppResources.sl.resx @@ -1299,7 +1299,7 @@ 1. Pojdite v nastavitve iOS aplikacije - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.sr.resx b/src/App/Resources/AppResources.sr.resx index d186a19d3..6dfa01853 100644 --- a/src/App/Resources/AppResources.sr.resx +++ b/src/App/Resources/AppResources.sr.resx @@ -2122,13 +2122,13 @@ Пребацили сте се на следећи доступни налог - Account Locked + Налог закључан - Account logged out successfully + Успешно одјављивање - Account removed successfully + Налог je успешно уклоњен Избриши Налог diff --git a/src/App/Resources/AppResources.ta.resx b/src/App/Resources/AppResources.ta.resx index a4712a580..fc1834bd9 100644 --- a/src/App/Resources/AppResources.ta.resx +++ b/src/App/Resources/AppResources.ta.resx @@ -1300,7 +1300,7 @@ ௧. iOS "அமைவுகள்" செயலிக்குச் செல் - 2. "கடவுச்சொற்கள் & கணக்குகள்"ஐத் தட்டு + 2. "கடவுச்சொற்கள்"ஐத் தட்டு 3. "கடவுச்சொற்கள் தன்னிரப்பல்"ஐத் தட்டு diff --git a/src/App/Resources/AppResources.th.resx b/src/App/Resources/AppResources.th.resx index 1a2a96b53..ba1d2c1d0 100644 --- a/src/App/Resources/AppResources.th.resx +++ b/src/App/Resources/AppResources.th.resx @@ -1299,7 +1299,7 @@ 1. Go to the iOS "Settings" app - 2. Tap "Passwords & Accounts" + 2. Tap "Passwords" 3. Tap "AutoFill Passwords" diff --git a/src/App/Resources/AppResources.uk.resx b/src/App/Resources/AppResources.uk.resx index 5629aa016..c072b6c95 100644 --- a/src/App/Resources/AppResources.uk.resx +++ b/src/App/Resources/AppResources.uk.resx @@ -276,16 +276,16 @@ Ви дійсно хочете вийти? - Remove Account + Вилучити обліковий запис - Are you sure you want to remove this account? + Ви дійсно хочете вилучити цей обліковий запис? - Account Already Added + Обліковий запис вже додано - Would you like to switch to it now? + Бажаєте перемкнутися на нього зараз? Головний пароль @@ -1162,10 +1162,10 @@ Служба спеціальних можливостей автозаповнення - Служба автозаповнення Bitwarden використовує Android Autofill Framework для заповнення паролів, кредитних карток та особистої інформації в інших програмах на вашому пристрої. + Служба автозаповнення Bitwarden використовує Android Autofill Framework для введення облікових даних в інших програмах на вашому пристрої. - Використовуйте службу автозаповнення Bitwarden, щоб автоматично вводити паролі, кредитні картки та особисту інформацію в інших програмах. + Використовуйте службу автозаповнення Bitwarden, щоб автоматично вводити облікові дані в інших програмах. Відкрити налаштування автозаповнення @@ -1935,7 +1935,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Деактивувати це відправлення для скасування доступу до нього. + Деактивувати це відправлення для скасування доступу до нього 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2003,7 +2003,7 @@ Спеціальний - Поділитися цим відправленням після збереження. + Поділитися цим відправленням після збереження 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2015,7 +2015,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Приховувати мою адресу електронної пошти від отримувачів. + Приховувати мою адресу електронної пошти від отримувачів На параметри відправлень впливають одна чи декілька політик організації. @@ -2105,28 +2105,28 @@ Одна чи декілька організаційних політик не дозволяють вам експортувати особисте сховище. - Add Account + Додати обліковий запис - Unlocked + Розблоковано - Locked + Заблоковано - Logged Out + Ви вийшли з системи - Switched to next available account + Ви перемкнулися на інший доступний обліковий запис - Account Locked + Обліковий запис заблоковано - Account logged out successfully + Ви успішно вийшли з облікового запису - Account removed successfully + Обліковий запис успішно вилучено Видалити обліковий запис From 1f58b0cabee3d9ba4a69c151c52040838b627776 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 12:12:08 +0200 Subject: [PATCH 065/100] Autosync the updated translations (#1868) Co-authored-by: github-actions <> --- src/App/Resources/AppResources.fi.resx | 2 +- src/App/Resources/AppResources.id.resx | 34 +- src/App/Resources/AppResources.nb.resx | 150 ++++----- src/App/Resources/AppResources.zh-Hant.resx | 326 ++++++++++---------- 4 files changed, 256 insertions(+), 256 deletions(-) diff --git a/src/App/Resources/AppResources.fi.resx b/src/App/Resources/AppResources.fi.resx index e80d567a0..c69d72d54 100644 --- a/src/App/Resources/AppResources.fi.resx +++ b/src/App/Resources/AppResources.fi.resx @@ -1299,7 +1299,7 @@ 1. Siirry iOS:n "Asetukset" -sovellukseen - 2. Napauta "Salasanat ja tilit" + 2. Napauta "Salasanat" 3. Napauta "Täytä salasanat automaattisesti" diff --git a/src/App/Resources/AppResources.id.resx b/src/App/Resources/AppResources.id.resx index 21a21589d..3db293049 100644 --- a/src/App/Resources/AppResources.id.resx +++ b/src/App/Resources/AppResources.id.resx @@ -276,13 +276,13 @@ Anda yakin ingin keluar? - Remove Account + Hapus Akun - Are you sure you want to remove this account? + Apakah anda yakin ingin menghapus akun ini? - Account Already Added + Akun telah ditambahkan Would you like to switch to it now? @@ -2105,31 +2105,31 @@ One or more organization policies prevents your from exporting your personal vault. - Add Account + Tambahkan Akun - Unlocked + Tidak terkunci - Locked + Terkunci - Logged Out + Keluar Switched to next available account - Account Locked + Akun terkunci - Account logged out successfully + Berhasil keluar Account removed successfully - Delete Account + Hapus akun Deleting your account is permanent @@ -2138,13 +2138,13 @@ Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue? - Deleting your account + Hapus akun anda - Your account has been permanently deleted + Akun Anda telah dihapus secara permanen. - Invalid Verification Code. + Kode verifikasi tidak valid. Request one-time password @@ -2162,18 +2162,18 @@ Sending code - Verifying + Memverifikasi... - Resend Code + Kirim ulang kode - A verification code was sent to your email + Kode verifikasi telah dikirim ke email Anda. An error occurred while sending a verification code to your email. Please try again - Enter the verification code that was sent to your email + Masukkan kode verifikasi yang dikirim ke email anda diff --git a/src/App/Resources/AppResources.nb.resx b/src/App/Resources/AppResources.nb.resx index 4a291a987..1adfcbb43 100644 --- a/src/App/Resources/AppResources.nb.resx +++ b/src/App/Resources/AppResources.nb.resx @@ -549,13 +549,13 @@ Umiddelbart - Tidsavbrudd i hvelvet + Tidsavbrudd for hvelvet - Handling ved pause i hvelvet + Hendelse ved tidsavbrudd for hvelvet - Hvis du logger ut, fjerner du all tilgang til hvelvet ditt og krever online godkjenning etter tidsavbrudd. Er du sikker på at du vil bruke denne innstillingen? + Dersom du logger ut vil all tilgang til hvelvet fjernes og du må autentisere online etter tidsavbruddet. Er du sikker på at du vil bruke denne innstillingen? Logger på... @@ -750,7 +750,7 @@ This is used for the autofill service. ex. "There are no items in your vault for twitter.com". - Når du velger et inntastingsfelt og ser Bitwardens tjeneste for automatiske utfylling, kan du trykke på den for å starte den automatisk utfyllingen. + Når du velger et inntastingsfelt og ser Bitwardens autofylltjeneste, kan du trykke på den for å starte autofylltjenesten. Trykk på denne beskjeden for å auto-utfylle en gjenstand fra ditt hvelv. @@ -774,7 +774,7 @@ Status - Den enkleste måten å legge til nye innlogginger i hvelvet ditt, er fra Bitwarden sin auto-utfyllingstjeneste. Lær mer om å bruke Bitwarden sin auto-utfyllingstjeneste ved å navigere til "Innstillinger". + Den enkleste måten å legge til nye innlogginger i hvelvet ditt, er fra Bitwarden sin autofylltjeneste. Lær mer om å bruke Bitwarden sin autofylltjeneste ved å navigere til "Innstillinger". Auto-utfylling @@ -1156,16 +1156,16 @@ Det er ingen gjenstander i denne mappen. - Det er ingen objekter i papirkurven. + Det er ingen elementer i papirkurven. Auto-utfyllingstilgjengelighetstjeneste - Bitwarden sin auto-utfyllingstjeneste bruker Android Autofill Framework for å bidra til å fylle inn innlogginger, bankkort og identifikasjonsinfo inn i andre apper på din enhet. + Bitwarden sin autofylltjeneste bruker Android Autofill Framework for å bidra til å fylle inn innlogginger, bankkort og identifikasjonsinfo inn i andre apper på din enhet. - Bruk Bitwarden sin auto-utfyllingstjeneste for å fylle ut innlogginger, bankkort og identifikasjonsinfo i andre apper. + Bruk Bitwarden sin autofylltjeneste for å fylle ut innlogginger, bankkort og identifikasjonsinfo i andre apper. Åpne auto-utfyllingsinnstillingene @@ -1199,7 +1199,7 @@ Skjult - Tilknyttet + Linket Tekst @@ -1329,10 +1329,10 @@ Innlogginger - Sikre notiser + Sikre notater - Alle gjenstander + Alle elementer URI-er @@ -1343,13 +1343,13 @@ A loading message when doing an exposed password check. - Sjekk om passordet har blitt utsatt. + Sjekk om passordet har blitt lekket. - Dette passordet har blitt utsatt {0} gang(er) i et databrudd. Du burde endre det. + Dette passordet har blitt eksponert {0} gang(er) i et datalekkasjer. Du bør endre det. - Dette passordet ble ikke funnet i noen kjente databrudd. Det burde være trygt å bruke. + Dette passordet ble ikke funnet i noen kjente datalekkasjer. Det bør være trygt å bruke. Identitetsnavn @@ -1364,10 +1364,10 @@ Typer - Det er ingen passord å liste opp. + Det er ingen passord å vise - Det er ingen gjenstander å liste opp. + Det er ingen elementer å vise. Søk i samling @@ -1388,13 +1388,13 @@ Flytt opp - Annet + Diverse Eierskap - Hvem eier dette objektet? + Hvem eier dette elementet? Det er ingen samlinger å liste opp. @@ -1404,7 +1404,7 @@ ex: Item moved to Organization. - Gjenstanden har blitt delt. + Elementet har blitt delt. Du må velge minst én samling. @@ -1413,13 +1413,13 @@ Del - Del objekt + Del element Flytt til organisasjon - Det er ingen organisasjoner å liste opp. + Det er ingen organisasjoner å vise. Velg en organisasjon som du ønsker å flytte denne gjenstanden til. Flytting til en organisasjon overfører eierskap til den aktuelle organisasjonen. Du vil ikke lenger være den direkte eieren av denne varen når den er flyttet. @@ -1431,7 +1431,7 @@ Passfrase - Ordadskiller + Orddeler Tøm @@ -1442,7 +1442,7 @@ Short for "Password Generator" - Det er ingen mapper å liste opp. + Det er ingen mapper å vise Fingeravtrykksfrase @@ -1474,10 +1474,10 @@ 30 minutter - Angi PIN-koden din for å låse opp Bitwarden. PIN-innstillingene tilbakestilles hvis du logger deg helt ut av programmet. + Angi PIN-koden for å låse opp Bitwarden. PIN-innstillingene dine blir tilbakestilt hvis du noen gang logger deg ut av applikasjonen. - Logget på som {0} gjennom {1}. + Logget på som {0} på {1}. ex: Logged in as user@example.com on bitwarden.com. @@ -1494,7 +1494,7 @@ A dark color - Lyst + Lys A light color @@ -1517,7 +1517,7 @@ Clipboard is the operating system thing where you copy/paste data to on your device. - Slett automatisk kopierte verdier fra utklippstavlen. + Slett kopierte verdier fra utklippstavlen automatisk. Clipboard is the operating system thing where you copy/paste data to on your device. @@ -1544,10 +1544,10 @@ Avslutt - Er du sikker på at du vil lukke Bitwarden? + Er du sikker på at du vil avslutte Bitwarden? - You you want to require unlocking with your master password when the application is restarted? + Ønsker du å låse opp ved å bruke hovedpassordet når applikasjonen startes på nytt? Svart @@ -1557,13 +1557,13 @@ Svartelistede URI-er - URIs that are blacklisted will not offer auto-fill. The list of apps should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android". + URI-er som er svartelistet vil ikke bli fylt ut automatisk. Listen over apper bør kommasepareres. F.eks.: "https://twitter.com, androidapp://com.twitter.android". - Skru av lagreforespørsel + Deaktiver lagreforespørsel - "Lagreforespørselen" spør deg automatisk om du vil lagre nye objekter i hvelvet ditt når du skriver dem inn for første gang. + "Lagreforespørselen" spør deg automatisk om du vil lagre nye elementer i hvelvet ditt når du skriver dem inn for første gang. Ved omstart av appen @@ -1579,7 +1579,7 @@ ex. Uppercase the first character of a word. - Inkluder nummer + Inkluder siffer Last ned @@ -1588,13 +1588,13 @@ Delt - Toggle Visiblity + Filsynlighet av/på - Innloggingsøkten din har utløpt. + Økten din har utløpt. - Bruk biometri for å bekrefte. + Biometrisk verifisering Biometri @@ -1627,7 +1627,7 @@ Filformat - Skriv inn ditt superpassordet for å eksportere dine hvelvdataer. + Skriv inn hovedpassordet for å eksportere hvelvdataene. Send en verifiseringskode til e-posten din @@ -1639,13 +1639,13 @@ Bekreft din identitet for å fortsette. - Denne eksporten inneholder hvelvdataene dine i et ukryptert format. Du skal ikke lagre eller sende den eksporterte filen over usikre kanaler (for eksempel e-post). Slett den umiddelbart etter at du er ferdig med å bruke den. + Denne eksporten inneholder hvelvdataene dine i et ukryptert format. Du bør ikke lagre eller sende den eksporterte filen over usikre kanaler (for eksempel e-post). Slett den umiddelbart etter at du er ferdig med å bruke den. Denne eksporten krypterer dataene dine ved hjelp av kontoen din sin krypteringsnøkkel. Hvis du noen gang endrer krypteringsnøkkelen til kontoen din, bør du eksportere dataene igjen, ettersom du da ikke vil kunne dekryptere denne eksportfilen. - Kontokrypteringsnøkler er unike for hver Bitwarden sin brukerkonto, og du kan ikke importere en kryptert eksport til en annen konto. + Kontokrypteringsnøkler er unike for hver Bitwarden sin brukerkonto, så du kan ikke importere en kryptert eksport til en annen konto. Bekreft eksport av hvelvet @@ -1688,7 +1688,7 @@ Message shown when interacting with the server - Varen ble sendt til papirkurven. + Elementet ble sendt til papirkurven. Confirmation message after successfully soft-deleting a login @@ -1700,7 +1700,7 @@ Message shown when interacting with the server - Varen har blitt gjenopprettet. + Elementet har blitt gjenopprettet. Confirmation message after successfully restoring a soft-deleted item @@ -1716,7 +1716,7 @@ Confirmation alert message when permanently deleteing a cipher. - Vil du virkelig gjenopprette denne gjenstanden? + Vil du virkelig gjenopprette dette elementet? Confirmation alert message when restoring a soft-deleted cipher. @@ -1751,7 +1751,7 @@ Angi hovedpassord - For å fullføre innloggingen med SSO, angi et superpassord for å få tilgang til og beskytte hvelvet ditt. + For å fullføre innloggingen med SSO, angi et hovedpassord for å få tilgang til og beskytte hvelvet ditt. En eller flere av organisasjonens vilkår krever hovedpassordet ditt for å oppfylle følgende krav: @@ -1763,13 +1763,13 @@ Minste lengde på {0} - Inneholder ett eller flere store tegn + Inneholder én eller flere store bokstaver - Inneholder ett eller flere små tegn + Inneholder én eller flere små bokstaver - Inneholde ett eller flere tall + Inneholde ett eller flere siffer Inneholder ett eller flere av de følgende spesialtegn: {0} @@ -1778,7 +1778,7 @@ Ugyldig passord - Passordet oppfyller ikke organisasjonens krav. Kontroller policyinformasjonen og prøv på nytt. + Passordet oppfyller ikke organisasjonens krav. Kontroller organisasjonens vilkår og prøv på nytt. Laster @@ -1788,13 +1788,13 @@ - Bruksvilkårene og personvernerklæring er ikke godkjent. + Vilkårene for bruk og personvernerklæring er ikke akseptert. Vilkår for bruk - Retningslinjer for personvern + Personvernsretningslinjer Bitwarden trenger oppmerksomhet – Aktiver "Draw-Over" i "Auto-utfyllingstjenester" fra Bitwarden-innstillinger @@ -1836,10 +1836,10 @@ Hvis aktivert, vil tilgjengelighet vise en popup for å forsterke Autofill Service for eldre apper som ikke støtter Android Autofill Framework. - På grunn av bedrifsretningslinjer er du begrenset fra å lagre objekter til ditt personlige hvelv. Endre alternativ for eierskap til en organisasjon og velg blant tilgjengelige samlinger. + På grunn av virksomhetsvilkår er du begrenset fra å lagre objekter til ditt personlige hvelv. Endre alternativ for eierskap til en organisasjon og velg blant tilgjengelige samlinger. - En bedriftsretningslinje påvirker dine eierskapsinnstillinger. + Organisasjonsvilkår påvirker dine eierskapsinnstillinger. Send @@ -1877,10 +1877,10 @@ Dato for sletting - Sletting tid + Slettetidspunkt - Send-en vil bli slettet permanent på den angitte dato og klokkeslett. + Send-en vil bli permanent slettet på angitte dato og klokkeslett. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1900,7 +1900,7 @@ Utløpt - Maksimal antall tilganger + Maksimalt antall tilganger Hvis satt, vil brukere ikke lenger ha tilgang til dette send når maksimal antall tilgang er nådd. @@ -1910,13 +1910,13 @@ Maksimalt antall tilganger nådd - Antall nåværende tilgang + Antall nåværende tilganger Nytt passord - Eventuelt krever et passord for brukere å få tilgang til denne Send. + Valgfritt passordkrav å få tilgang til denne Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1940,18 +1940,18 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Det er ingen sendinger på kontoen din. + Det er ingen Send-er i kontoen din. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Legg til en sendt + Legg til en Send 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. Kopier lenke - Del link + Del lenke Send lenke @@ -2008,7 +2008,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - På grunn av en virksomhetsregel kan du kun slette en eksisterende Send. + På grunn av virksomhetsvilkår kan du kun slette en eksisterende Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2019,11 +2019,11 @@ Skjul min e-postadresse fra mottakere. - En eller flere av organisasjons retningslinjer påvirker generatorinnstillingene dine. + En eller flere av organisasjons vilkår påvirker generatorinnstillingene dine. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Gratis kontoer kan bare dele tekst. Et premiummedlemskap kreves for å bruke filer med Send. + Gratiskontoer kan kun dele tekst. Et premiummedlemskap kreves for å bruke filer med Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2034,13 +2034,13 @@ Forespørsel om hovedpassord på nytt - Superpassord bekreftelse + Hovedpassord bekreftelse - Denne handlingen er beskyttet, for å fortsette å skrive inn superpassordet på nytt for å verifisere din identitet. + Denne handlingen er beskyttet, for å fortsette å skrive inn hovedpassordet på nytt for å verifisere din identitet. - Bekreftelsekode kreves + Captcha kreves Captcha feilet. Prøv på nytt. @@ -2094,16 +2094,16 @@ Sørg for at standardnettleseren din støtter WebAuthn og prøv igjen. - Denne organisasjonen har en bedriftsoppsettsregel som automatisk innrullerer deg i tilbakestilling av passord. Registrering vil tillate organisasjonsadministratorer å endre hovedpassordet ditt. + Denne organisasjonen har en virksomhetsvilkår som automatisk innrullerer deg i tilbakestilling av passord. Registrering vil tillate organisasjonsadministratorer å endre hovedpassordet ditt. - Din organisasjons retningslinjer påvirker tidsavbruddet for hvelvet. Maksimalt tillatt tidsavbrudd for hvelv er {0} time(r) og {1} minutt(er) + Din organisasjons vikår påvirker tidsavbruddet for hvelvet. Maksimalt tillatt tidsavbrudd for hvelv er {0} time(r) og {1} minutt(er) Tidsavbruddet ditt for hvelvet overstiger begrensningene som er satt av organisasjonen din. - En eller flere organisasjonsoppsettsregler hindrer deg i å eksportere ditt personlige hvelv. + En eller flere organisasjonvilkår hindrer deg i å eksportere ditt personlige hvelv. Legg til konto @@ -2115,19 +2115,19 @@ Låst - Logged Out + Logget av - Switched to next available account + Byttet til neste tilgjengelige konto - Account Locked + Konto låst - Account logged out successfully + Kontoen er logget av - Account removed successfully + Kontoen er fjernet Slett konto @@ -2172,9 +2172,9 @@ En verifiseringskode er sendt til din e-post - En feil oppstod ved sending av en verifiseringskode til e-posten. Vennligst prøv igjen + En feil oppstod ved sending av verifiseringskode til e-posten. Vennligst prøv igjen - Skriv inn bekreftelseskoden som ble sendt til din e-post + Skriv inn verifiseringskoden som ble sendt til din e-post diff --git a/src/App/Resources/AppResources.zh-Hant.resx b/src/App/Resources/AppResources.zh-Hant.resx index 0b4652f68..8d244049c 100644 --- a/src/App/Resources/AppResources.zh-Hant.resx +++ b/src/App/Resources/AppResources.zh-Hant.resx @@ -172,7 +172,7 @@ Message shown when interacting with the server - 確定要刪除嗎?刪除後將無法復原。 + 您確定要刪除嗎?刪除後將無法復原。 Confirmation alert message when deleteing something. @@ -219,7 +219,7 @@ 已新增資料夾。 - 資料夾已刪除。 + 已刪除資料夾。 (未分類) @@ -229,7 +229,7 @@ 資料夾 - 資料夾已更新。 + 已更新資料夾。 前往網站 @@ -243,7 +243,7 @@ Hide a secret value that is currently shown (password). - 繼續之前,請先連線至網際網路。 + 請先連線至網際網路再繼續。 Description message for the alert when internet connection is required to continue. @@ -251,10 +251,10 @@ Title for the alert when internet connection is required to continue. - 主密碼不正確,請重試。 + 主密碼不正確,請再試一次。 - PIN 碼不正確,請重試。 + PIN 碼不正確,請再試一次。 啟動 @@ -279,13 +279,13 @@ 移除帳戶 - 確定要移除這個帳戶嗎? + 您確定要移除這個帳戶嗎? - 帳戶已加入 + 已新增帳戶 - 您想現在就切換到它嗎? + 您想現在就切換過去嗎? 主密碼 @@ -296,7 +296,7 @@ Text to define that there are more options things to see. - 我的密碼庫 + 密碼庫 The title for the vault page. @@ -338,7 +338,7 @@ Reveal a hidden value (password). - 項目已被刪除。 + 已刪除項目。 Confirmation message after successfully deleting a login. @@ -367,7 +367,7 @@ Label for a username. - {0} 欄位必須填入。 + 必須填入 {0} 欄位。 Validation message for when a form field is left blank and is required to be entered. @@ -390,7 +390,7 @@ 檢視 - 瀏覽我們的網站 + 前往我們的網站 瀏覽我們的網站以取得協助、了解新消息、傳送電子郵件給我們或瀏覽更多有關使用 Bitwarden 的秘訣。 @@ -412,7 +412,7 @@ 新增項目 - App 擴充套件 + App 延伸功能 使用 Bitwarden 無障礙服務在應用程式和網站中自動填入您的登入資料。 @@ -424,10 +424,10 @@ 避免易混淆的字元 - Bitwarden App 擴充套件 + Bitwarden App 延伸功能 - Bitwarden App 擴充套件是新增登入資料到密碼庫的最簡單方法。請至「設定」頁面了解更多關於 Bitwarden App 擴充套件的使用資訊。 + Bitwarden App 延伸功能是新增登入資料至密碼庫的最簡單方法。請至「設定」頁面了解更多關於 Bitwarden App 延伸功能的使用資訊。 在 Safari 和其他應用程式中使用 Bitwarden 自動填入登入資料。 @@ -473,13 +473,13 @@ 請輸入您的帳户電子郵件地址以接收主密碼提示。 - 重新啟用 App 擴充套件 + 重新啟用 App 延伸功能 快要完成了! - 啟用 App 擴充套件 + 啟用 App 延伸功能 在 Safari 中,使用分享圖示尋找 Bitwarden(提示:在選單最底行的右邊)。 @@ -498,7 +498,7 @@ 在 Safari 或 Chrome 中,使用分享圖示尋找 Bitwarden(提示:在分享選單的最底行的右邊)。 - 在選單中按一下 Bitwarden 圖示以啟動擴充套件。 + 在選單中按一下 Bitwarden 圖示以啟動延伸功能。 要在 Safari 或其他程式中開啟 Bitwarden ,請點選選單底部的「更多」圖示。 @@ -546,23 +546,23 @@ 4 小時 - 即時 + 立即 - 密碼庫逾時時長 + 密碼庫逾時時間 密碼庫逾時動作 - 選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,以及重新認證時需要連線網路。確定要使用此設定嗎? + 選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,若要重新驗證則需連線網路。確定要使用此設定嗎? 正在登入... Message shown when interacting with the server - 登入或建立帳戶來存取您的安全密碼庫。 + 登入或建立帳戶以存取您的安全密碼庫。 管理 @@ -571,7 +571,7 @@ 兩次填寫的主密碼不一致。 - 主密碼是您用於存取您的密碼庫的密碼。不要忘記主密碼,這一點非常重要。如果忘記了密碼,無法將其復原。 + 主密碼是用於存取密碼庫的密碼。它非常重要,請您不要忘記它。若您忘記了主密碼,沒有任何方法能將其復原。 主密碼提示(選用) @@ -594,7 +594,7 @@ 更多設定 - 您必須先登入 Bitwarden 才可使用擴充套件。 + 您必須先登入 Bitwarden 才可使用延伸功能。 永不 @@ -619,7 +619,7 @@ Confirmation, like "Ok, I understand it" - 選項預設值是通過 Bitwarden 程式的密碼產生器設定的。 + 選項預設值是透過 Bitwarden 程式的密碼產生器設定的。 選項 @@ -628,7 +628,7 @@ 其他 - 密碼已產生。 + 已產生密碼。 密碼產生器 @@ -677,7 +677,7 @@ 項目資訊 - 項目已更新。 + 已更新項目。 正在送出... @@ -704,7 +704,7 @@ 兩步驟登入 - 兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這將使您的帳戶更加安全。兩步驟登入可以在 Bitwarden 網頁版密碼庫啟用。要現在前往嗎? + 兩步驟登入需要您從其他裝置(例如安全金鑰、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 Bitwarden 網頁版密碼庫啟用。要現在前往嗎? 使用 {0} 解鎖 @@ -733,7 +733,7 @@ Screen title - 擴充套件已啟用! + 已啟用延伸功能! 圖示 @@ -762,7 +762,7 @@ 1. 在 Android 的無障礙設定畫面中,按一下服務標題下的「Bitwarden」。 - 2. 打開開關,然後按“確定”。 + 2. 開啟開關,然後按「確定」。 已停用 @@ -774,16 +774,16 @@ 狀態 - Bitwarden 自動填入服務是新增登入資料的最簡單方法。請到「設定」頁面了解更多有關使用 Bitwarden 自動填入服務的資訊 。 + Bitwarden 自動填入服務是新增登入資料的最簡單方法。請至「設定」頁面深入了解使用 Bitwarden 自動填入服務的方法 。 自動填入 - 您想自動填入還是檢視登入資料? + 您想自動填入還是檢視此項目? - 確定要自動填入此資料嗎?它與「{0}」不完全一致。 + 您確定要自動填入此項目嗎?它與「{0}」不完全一致。 相符的項目 @@ -813,7 +813,7 @@ For 2FA - 輸入已傳送至電子郵件地址 {0} 的 6 位數驗證碼。 + 輸入已傳送至電子郵件信箱 {0} 的 6 位數驗證碼。 For 2FA @@ -824,7 +824,7 @@ 此帳戶已啟用兩步驟登入,但是本裝置不支援已設定的兩步驟登入方式。請使用已支援的裝置,及/或新增可以更好地跨裝置的兩步驟登入方式(例如驗證器應用程式)。 - 復原代碼 + 復原碼 For 2FA @@ -839,10 +839,10 @@ 兩步驟登入選項 - 使用另一種兩步驟登入方式 + 使用另一種兩步驟登入方法 - 無法傳送​​驗證電子郵件。再試一次。 + 無法傳送​​驗證電子郵件。請再試一次。 For 2FA @@ -850,10 +850,10 @@ For 2FA - 要繼續的話,請將您的 YubiKey 靠在裝置的背面或者將 YubiKey 插入您裝置的 USB 連接埠,然後按下按鈕。 + 若要繼續,請將您的 YubiKey 靠在裝置的背面或者將 YubiKey 插入您裝置的 USB 連接埠,然後按下按鈕。 - YubiKey 安全鑰匙 + YubiKey 安全金鑰 "YubiKey" is the product name and should not be translated. @@ -873,7 +873,7 @@ Message shown when downloading a file - 這個附件大小為 {0} 。確定要下載到您的裝置嗎? + 這個附件大小為 {0} 。確定要下載至您的裝置嗎? The placeholder will show the file size of the attachment. Ex "25 MB" @@ -896,7 +896,7 @@ 將相機對準 QR code 。 - 掃描 QR 碼 + 掃描 QR Code 相機 @@ -908,7 +908,7 @@ 複製 TOTP - 如果您的登入資料已包含驗證器金鑰,TOTP 驗證碼會在您自動填入時自動複製到您的剪貼簿。 + 若您的登入資料已包含驗證器金鑰,TOTP 驗證碼會在您自動填入時自動複製至您的剪貼簿。 停用自動 TOTP 複製 @@ -947,7 +947,7 @@ 更新加密金鑰前不能使用此功能。 - 了解更多 + 深入了解 API 伺服器 URL @@ -959,7 +959,7 @@ 適用於進階使用者。您可以單獨指定各個服務的基礎 URL。 - 環境 URL 已儲存。 + 已儲存環境 URL。 {0} 的格式不正確。 @@ -970,7 +970,7 @@ "Identity" refers to an identity server. See more context here https://en.wikipedia.org/wiki/Identity_management - 自我託管環境 + 自我裝載環境 指定您內部部署的 Bitwarden 安裝之基礎 URL。 @@ -1048,10 +1048,10 @@ Dr - 到期月份 + 逾期月份 - 到期年份 + 逾期年份 二月 @@ -1126,10 +1126,10 @@ 地址 - 到期 + 逾期 - 停用網站圖示 + 停用網站圖示顯示功能 在您密碼庫的每個登入資料旁顯示一個可辨識的圖示。 @@ -1144,7 +1144,7 @@ 密碼庫已鎖定 - 前往我的密碼庫 + 前往密碼庫 集合 @@ -1162,13 +1162,13 @@ 自動填入無障礙服務 - Bitwarden 自動填入服務使用 Android 自動填入框架來協助將登入信息填入至你裝置上的其他應用程式中。 + Bitwarden 自動填入服務使用 Android 自動填入框架來協助您將登入資訊填入至您裝置上的其他應用程式中。 - 使用 Bitwarden 自動填入服務將登入信息填入到其他應用程式中。 + 使用 Bitwarden 自動填入服務將登入資訊填入至其他應用程式中。 - 打開自動填入設定 + 開啟自動填入設定 Face ID @@ -1264,13 +1264,13 @@ 再試一次 - 要繼續的話,請將您的 YubiKey 靠在裝置的背面。 + 若要繼續,請將您的 YubiKey 靠在裝置的背面。 - 當應用程式不支援標準的自動填入服務時,無障礙服務或許能夠幫助使用。 + 當應用程式不支援標準的自動填入服務時,無障礙服務或許能夠幫助到您。 - 密碼已更新 + 密碼更新於 ex. Date this password was updated @@ -1278,16 +1278,16 @@ ex. Date this item was updated - 自動填入已啟用! + 已啟用自動填入! 您必須先登入 Bitwarden 應用程式,才可以使用自動填入功能。 - 登入應用程式和網站時,您可以透過鍵盤立即輕鬆存取您的登入資料。 + 登入應用程式和網站時,您可以透過鍵盤輕鬆存取您的登入資料。 - 建議您在“設定”中關閉您不再使用的其他自動填入應用程式。 + 建議您在「設定」中關閉您不再使用的其他自動填入應用程式。 您可以直接透過鍵盤存取密碼庫以快速的自動填入密碼。 @@ -1299,7 +1299,7 @@ 1. 前往 iOS 的「設定」應用程式 - 2. 點選「密碼和帳戶」 + 2. 點選「密碼」 3. 點選「自動填入密碼」 @@ -1314,7 +1314,7 @@ 密碼自動填入 - 使用 Bitwarden 密碼自動填入擴充套件是新增登入資料到密碼庫的最簡單方法。請至「設定」頁面了解更多關於 Bitwarden 密碼自動填入擴充套件的使用資訊。 + 使用 Bitwarden 密碼自動填入延伸功能是新增登入資料至密碼庫的最簡單方法。請至「設定」頁面了解更多關於 Bitwarden 密碼自動填入延伸功能的使用資訊。 無效的電子郵件地址。 @@ -1364,13 +1364,13 @@ 類型 - 沒有可顯示的密碼。 + 沒有可列出的密碼。 - 沒有可顯示的項目。 + 沒有可列出的項目。 - 搜尋收藏 + 搜尋集合 搜尋資料夾 @@ -1404,25 +1404,25 @@ ex: Item moved to Organization. - 項目已共享。 + 已共用項目。 您必須至少選擇一個集合。 - 共享 + 共用 - 分享項目 + 共用項目 移動至組織 - 沒有可顯示的組織。 + 沒有可列出的組織。 - 選擇您希望將這個項目移動到哪個組織。項目的擁有權將會轉移到該組織。一經移動,您將不再是此項目的直接擁有者。 + 選擇您希望將這個項目移動至哪個組織。項目的擁有權將會轉移至該組織。轉移之後,您將不再是此項目的直接擁有者。 字數 @@ -1442,7 +1442,7 @@ Short for "Password Generator" - 沒有可顯示的資料夾。 + 沒有可列出的資料夾。 指紋短語 @@ -1481,13 +1481,13 @@ ex: Logged in as user@example.com on bitwarden.com. - 密碼庫已鎖定。驗證主密碼以繼續。 + 密碼庫已鎖定。請驗證主密碼以繼續。 - 密碼庫已鎖定。驗證 PIN 碼以繼續。 + 密碼庫已鎖定。請驗證 PIN 碼以繼續。 - 您的密碼庫已上鎖。請驗證身份後繼續。 + 您的密碼庫已鎖定。請驗證身分以繼續。 深色 @@ -1535,7 +1535,7 @@ 變更應用程式的主題色彩。 - 預設 (系統) + 預設(系統) 複製備註 @@ -1547,7 +1547,7 @@ 您確定要結束 Bitwarden 嗎? - 當應用程式重新啟動時,是否要求用主密碼來解鎖? + 當應用程式重新啟動時,是否要求使用主密碼解鎖? 黑色 @@ -1557,22 +1557,22 @@ 黑名單 URI - 被加到黑名單的網址將不提供自動填入。應用程式串列必須以逗號分隔。範例:「https://twitter.com, androidapp://com.twitter.android」。 + 列入黑名單的網址將不提供自動填入。應用程式清單必須以逗號分隔。範例:「https://twitter.com, androidapp://com.twitter.android」。 停用儲存提示 - 當您首次輸入時,「儲存提示」會自動提示您將新項目儲存進密碼庫。 + 當您首次輸入時,「儲存提示」會自動提醒您將新項目儲存至密碼庫。 於程式重新啟動時 - 「自動填入」使其他網站及應用程式能更簡單地安全存取您的 Bitwarden 密碼庫。看起來您尚未啟用 Bitwarden 的自動填入服務。請在「設定」熒幕啟用 Bitwarden 的自動填入。 + 「自動填入」使其他網站及應用程式能更簡單地安全存取您的 Bitwarden 密碼庫。您目前尚未啟用 Bitwarden 自動填入服務。請在「設定」畫面啟用 Bitwarden 自動填入服務。 - App 重新啟動之後,您的主題變更將會被套用。 + 主題變更將於重新啟動應用程式後生效。 大寫 @@ -1588,7 +1588,7 @@ 共用 - 切換顯示 + 切換可見度 您的登入階段已過期。 @@ -1606,7 +1606,7 @@ Bitwarden 需要注意 - 請到 Bitwarden 設定中查看「自動填入無障礙服務」 - 3. 在 Android 應用程式設定介面找到 Bitwarden,進入「顯示在其他應用程式上層」選項(在「進階」下),點擊開關以開啟叠加層支持。 + 3. 在 Android 應用程式設定介面找到 Bitwarden,進入「顯示在其他應用程式上層」選項(在「進階」下),點選開關以啟用叠加層支援。 權限 @@ -1615,13 +1615,13 @@ 開啟疊加層權限設定 - 3. 在 Android 應用程式設定介面找到 Bitwarden,選擇「顯示在其他應用程式上層」(在「進階」下),並打開開關以允許叠加層。 + 3. 在 Android 應用程式設定介面找到 Bitwarden,選擇「顯示在其他應用程式上層」(在「進階」下),並開啟開關以允許叠加層。 拒絕 - 授予 + 已授予 檔案格式 @@ -1630,7 +1630,7 @@ 輸入您的主密碼以匯出密碼庫資料。 - 傳送驗證碼到您的信箱 + 傳送驗證碼至您的電子郵件信箱 驗證碼已傳送! @@ -1639,13 +1639,13 @@ 請先確認身分後再繼續。 - 此匯出包含未加密格式的密碼庫檔案。您不應將它存放或經由不安全的方式(例如電子郵件)傳送。用完後請立即將它刪除。 + 此次匯出的密碼庫檔案為未加密格式。您不應將它存放或經由不安全的方式(例如電子郵件)傳送。用完後請立即將它刪除。 - 將使用您帳戶的加密金鑰來加密匯出資料,若您更新了帳戶的加密金鑰,請重新匯出,才有辦法解密匯出的檔案。 + 將使用您帳戶的加密金鑰來加密匯出的資料,若您更新了帳戶的加密金鑰,請重新匯出,否則將無法解密匯出的檔案。 - 每個 Bitwarden 使用者帳戶的帳戶加密金鑰都不同,因此無法將加密過的匯出檔案匯入到不同帳戶中。 + 每個 Bitwarden 使用者帳戶的帳戶加密金鑰都不相同,因此無法將已加密匯出的檔案匯入至不同帳戶中。 確認匯出密碼庫 @@ -1658,24 +1658,24 @@ 匯出您的密碼庫時發生錯誤。若問題持續發生,請改用網頁版密碼庫匯出資料。 - 成功匯出密碼庫 + 已成功匯出密碼庫 複製 Clone an entity (verb). - 一個或多個組織原则正影響密碼產生器設定。 + 一個或多個組織原則正影響密碼產生器設定。 - 打開 + 開啟 Button text for an open operation (verb). 儲存附件時發生錯誤。若問題持續發生,請改用網頁版密碼庫儲存附件。 - 附件已成功儲存 + 已成功儲存附件 請到 Bitwarden 設定中開啟「自動填入無障礙服務」以使用自動填入功能。 @@ -1684,23 +1684,23 @@ 未偵測到密碼欄位 - 正在傳送到垃圾桶… + 正在傳送至垃圾桶… Message shown when interacting with the server - 項目已發送到垃圾桶。 + 已將項目傳送至垃圾桶。 Confirmation message after successfully soft-deleting a login - 恢復 + 還原 Restores an entity (verb). - 恢復中… + 正在還原... Message shown when interacting with the server - 項目已恢復。 + 已還原項目。 Confirmation message after successfully restoring a soft-deleted item @@ -1716,21 +1716,21 @@ Confirmation alert message when permanently deleteing a cipher. - 您確定要恢復此項目嗎? + 您確定要還原此項目嗎? Confirmation alert message when restoring a soft-deleted cipher. - 您確定要發送到垃圾桶嗎? + 您確定要傳送至垃圾桶嗎? Confirmation alert message when soft-deleting a cipher. - 已停用生物辨識解鎖功能,請先驗證主密碼。 + 已停用生物特徵辨識解鎖功能,請先驗證主密碼。 - 在驗證主密碼之前,用於自動填入的生物辨識功能將暫時停用。 + 在驗證主密碼之前,用於自動填入的生物特徵辨識功能將暫時停用。 - 啓用刷新時同步 + 啟用重新整理時同步 使用下拉手勢同步密碼庫。 @@ -1739,22 +1739,22 @@ 企業單一登入 - 要使用組織的單一登入入口快速登入。請輸入您組織的識別符以開始。 + 若要使用組織的單一登入入口快速登入。請先輸入您的組織識別碼。 - 組織識別符 + 組織識別碼 - 目前無法透過 SSO 登入方式登入 + 目前無法使用 SSO 登入 設定主密碼 - 要完成 SSO 登入,請設定一個主密碼以存取和保護您的密碼庫。 + 要完成 SSO 登入設定,請設定一組主密碼以存取和保護您的密碼庫。 - 一個或多個組織原则要求您的主密碼須符合下列條件: + 一個或多個組織原則要求您的主密碼須符合下列條件: 最小複雜度為 {0} @@ -1778,16 +1778,16 @@ 密碼無效 - 輸入的密碼不符合組織要求,請確認原则資訊然後再試一次。 + 輸入的密碼不符合組織要求,請確認原則資訊後再試一次。 - 載入中 + 正在載入 - 開啟此開關,代表您同意下列項目: + 開啟此開關,代表您同意下列項目: - 尚未接受服務條款與隱私權保護政策。 + 尚未接受服務條款與隱私權政策。 服務條款 @@ -1796,7 +1796,7 @@ 隱私權政策 - Bitwarden 需要注意 - 請到 Bitwarden 設定的「自動填入服務」中啓用「Draw-Over」 + Bitwarden 需要注意 - 請到 Bitwarden 設定的「自動填入服務」中啟用「Draw-Over」 自動填入服務 @@ -1805,40 +1805,40 @@ 使用內嵌式自動填入 - 若您的 IME(鍵盤)支援,將使用內嵌式自動填入。若您的設定不支援(或未啟用此選項),將使用預設的覆疊式自動填入。 + 若您的 IME(鍵盤)支援,將使用內嵌式自動填入。若您的設定不支援(或未啟用此選項),將使用預設的疊加式自動填入。 使用無障礙 - 使用 Bitwarden 無障礙服務在應用程式和網路上自動填入您的登入資料。 啟用後,當選擇登入欄位時,將顯示一個彈出視窗。 + 使用 Bitwarden 無障礙服務在應用程式和網站上自動填入您的登入資料。 啟用後,將在選擇登入欄位時顯示彈出式視窗。 - 使用 Bitwarden 無障礙服務在應用程式和網路上自動填入您的登入資料。(還需要開啟 Draw-Over) + 使用 Bitwarden 無障礙服務在應用程式和網站上自動填入您的登入資料。(需同時啟用 Draw-Over 功能) - 使用 Bitwarden 無障礙服務使用自動填入快速操作磁貼,和/或使用 Draw-Over 顯示彈出視窗(如果已開啟)。 + 使用 Bitwarden 無障礙服務來使用自動填入快速控制圖塊,和/或使用 Draw-Over 來顯示彈出式視窗(如果已開啟)。 - 需要使用自動填入快速操作磁貼或透過使用 Draw-Over(如果已開啟)來增強自動填入服務。 + 需要使用自動填入快速控制圖塊,或透過使用 Draw-Over(如果已開啟)來增強自動填入服務。 使用 Draw-Over - 啓用後,在選擇登入欄位時,將允許 Bitwarden 無障礙服務顯示一個彈出視窗。 + 啟用後,在選擇登入欄位時將允許 Bitwarden 無障礙服務顯示彈出式視窗。 - 若啓用,儅選擇登入欄位自動填入您的登入資料時,Bitwarden 無障礙服務將顯示一個彈出視窗。 + 若啟用,當選擇登入欄位自動填入您的登入資料時,Bitwarden 無障礙服務將顯示一個彈出式視窗。 - 若啓用,無障礙將顯示一個彈出視窗以增强不支援 Android 自動填入框架的舊應用程式的自動填入服務。 + 若啟用,無障礙將顯示一個彈出式視窗以增強不支援 Android 自動填入框架的舊應用程式的自動填入服務。 - 由於某個企業原则,您被限制為儲存項目到您的個人密碼庫。將擁有權變更爲組織,並從可用的集合中選擇。 + 由於某個企業原則,您被限制為儲存項目至您的個人密碼庫。將擁有權變更為組織,並從可用的集合中選擇。 - 一個組織原则正影響您的擁有權選項。 + 組織原則正在影響您的擁有權選項。 Send @@ -1853,7 +1853,7 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 用於描述此 Send 的友好名稱。 + 用於描述此 Send 的易記名稱。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1863,7 +1863,7 @@ 您想要傳送的文字。 - 存取此 Send 時,預設將隱藏文字 + 存取此 Send 時,將預設隱藏文字 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1879,11 +1879,11 @@ 刪除時間 - 此 Send 將在指定的日期和時間被永久删除。 + 此 Send 將在指定的日期和時間後被永久刪除。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 等待刪除中 + 等待刪除 逾期日期 @@ -1912,10 +1912,10 @@ 目前存取次數 - 新的密碼 + 新密碼 - 可選。使用者需提供密碼才能存取此 Send。 + 選用。使用者需提供密碼才能存取此 Send。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1928,14 +1928,14 @@ 正在移除密碼 - 密碼已移除 + 已移除密碼。 - 關於此 Send 的私人注釋。 + 關於此 Send 的私人備註。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 停用此 Send 以阻止任何人存取它 + 停用此 Send 以阻止任何人存取 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1950,7 +1950,7 @@ 複製連結 - 分享連結 + 共用連結 Send 連結 @@ -1973,15 +1973,15 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Send 已刪除。 + 已刪除 Send。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Send 已更新。 + 已更新 Send。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 新的 Send 已創建。 + 已建立新的 Send。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2003,11 +2003,11 @@ 自訂 - 儲存時分享此 Send + 儲存時共用此 Send 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 由於某個企業原则,您只能刪除已有的 Send。 + 由於企業原則限制,您只能刪除現有的 Send。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2015,18 +2015,18 @@ 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 對收件人隱藏我的電子郵件位址 + 對收件人隱藏我的電子郵件地址 - 一個或多個組織原则正影響您的 Send 選項。 + 一個或多個組織原則正影響您的 Send 選項。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 免費帳戶僅限於共享文字。要使用文檔 Send,需要進階會員資格。 + 免費帳戶僅限於共用文字。若想使用檔案 Send,需要進階會員資格。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - 必須驗證您的電子郵件才能使用文檔 Send。你可以在網頁密碼庫中驗證您的電子郵件。 + 您必須驗證電子郵件才能夠使用檔案 Send。您可以在網頁版密碼庫中驗證您的電子郵件。 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2036,13 +2036,13 @@ 確認主密碼 - 此動作受到保護。若要繼續,請重新輸入您的主密碼以驗證您的身份。 + 此動作受到保護。若要繼續,請重新輸入您的主密碼以驗證您的身分。 - 要求人機驗證 + 需要人機驗證 (Captcha) - 人機驗證失敗,請重試。 + 人機驗證 (Captcha) 失敗,請再試一次。 已更新主密碼 @@ -2051,22 +2051,22 @@ 更新主密碼 - 您的主密碼最近被您的組織管理者變更過。要存取密碼庫,您必須現在更新主密碼。繼續操作會登出目前的登入階段,要求您重新登入。其他裝置上使用中的登入階段可能持續最長一個小時。 + 您的主密碼最近被您的組織管理者變更過。您必須現在更新主密碼才能存取密碼庫。繼續操作會登出您目前的工作階段,並要求您重新登入帳戶。其他裝置上的活動工作階段最多會保持一個小時。 正在更新密碼 - 當前無法更新密碼 + 目前無法更新密碼 移除主密碼 - {0} 正使用客戶管理加密的 SSO。繼續操作將從您的帳戶中移除主密碼並要求 SSO 登入。 + {0} 正在使用客戶管理加密的 SSO。繼續操作將移除您帳戶的主密碼並要求 SSO 登入。 - 如果您不想移除主密碼,您可以離開這個組織。 + 若您不想移除主密碼,您可以離開此組織。 離開組織 @@ -2081,7 +2081,7 @@ 若要繼續,請準備好您已經啟用 FIDO2 WebAuthn 的安全金鑰,在下個畫面按下 [驗證 WebAuthn],接著遵循指引。 - 使用 FIDO2 WebAuthn 認證,您可以使用外部安全金鑰進行認證。 + 使用 FIDO2 WebAuthn 驗證,您可以使用外部安全金鑰進行驗證。 驗證 WebAuthn @@ -2090,19 +2090,19 @@ 返回應用程式 - 請確保您的預設瀏覽器支援 WebAuthn,然後重試。 + 請確保您的預設瀏覽器支援 WebAuthn,然後再試一次。 此組織有一個可以為您自動註冊密碼重設的企業原則。註冊後將允許組織管理員變更您的主密碼。 - 您的組織策略正在影響您的密碼庫逾時時長。密碼庫逾時時長最多可以設定到 {0} 小時,{1} 分鐘。 + 您的組織原則正在影響您的密碼庫逾時時間。密碼庫逾時時間最多可以設定到 {0} 小時 {1} 分鐘。 - 您的密碼庫逾時時長超過組織限制。 + 您的密碼庫逾時時間超過組織設定的限制。 - 一或多個組織策略不允許您匯出個人密碼庫。 + 一個或多個組織原則禁止您匯出個人密碼庫。 新增帳戶 @@ -2117,25 +2117,25 @@ 已登出 - 已切換到下一個可用的帳戶 + 已切換至下一個可用的帳戶 帳戶已鎖定 - 帳戶已成功登出 + 已成功登出帳戶 - 帳戶已成功移除 + 已成功移除帳戶 刪除帳戶 - 「刪除帳戶」不可逆 + 刪除帳戶是永久性的 - 您的帳戶及相關資料都會被清得一乾二凈,無法復原。是否繼續? + 您的帳戶及相關資料將被完全清除並且無法復原。您確定要繼續嗎? 正在刪除您的帳戶 @@ -2147,13 +2147,13 @@ 無效的驗證碼。 - 請求一次性密碼 + 要求一次性密碼 傳送驗證碼 - 傳送中 + 正在傳送 儲存時複製 Send 連結 @@ -2168,12 +2168,12 @@ 重新傳送驗證碼 - 驗證碼已傳送到您的電子信箱 + 已傳送驗證碼至您的電子郵件信箱 - 將驗證碼傳送至您的電子信箱時發生錯誤。請重試 + 將驗證碼傳送至您的電子郵件信箱時發生錯誤。請再試一次 - 輸入傳送到您電子信箱的驗證碼 + 輸入傳送至您電子郵件信箱的驗證碼 From 88f6b60b97b1a08e437213fde1db3efdfda125a3 Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Fri, 1 Apr 2022 12:07:14 -0400 Subject: [PATCH 066/100] Crash fixes (#1869) * Crash fixes * added HasAutofillService to DeviceActionService --- src/Android/Services/BiometricService.cs | 19 +++++++++---------- src/Android/Services/DeviceActionService.cs | 7 ++++++- src/App/Abstractions/IDeviceActionService.cs | 1 + .../Settings/AutofillServicesPageViewModel.cs | 3 ++- .../Vault/AutofillCiphersPageViewModel.cs | 4 ++-- src/Core/Services/TokenService.cs | 8 ++++---- src/iOS.Core/Services/DeviceActionService.cs | 5 +++++ 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Android/Services/BiometricService.cs b/src/Android/Services/BiometricService.cs index aa23477ba..c55673f19 100644 --- a/src/Android/Services/BiometricService.cs +++ b/src/Android/Services/BiometricService.cs @@ -43,23 +43,22 @@ namespace Bit.Droid.Services public Task ValidateIntegrityAsync(string bioIntegrityKey = null) { - // bioIntegrityKey used in iOS only if (Build.VERSION.SdkInt < BuildVersionCodes.M) { return Task.FromResult(true); } - _keystore.Load(null); - IKey key = _keystore.GetKey(KeyName, null); - Cipher cipher = Cipher.GetInstance(Transformation); - - if (key == null || cipher == null) - { - return Task.FromResult(true); - } - try { + _keystore.Load(null); + var key = _keystore.GetKey(KeyName, null); + var cipher = Cipher.GetInstance(Transformation); + + if (key == null || cipher == null) + { + return Task.FromResult(true); + } + cipher.Init(CipherMode.EncryptMode, key); } catch (KeyPermanentlyInvalidatedException e) diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index 11ef29774..37d90d0d4 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -674,7 +674,7 @@ namespace Bit.Droid.Services else { var data = new Intent(); - if (cipher == null) + if (cipher?.Login == null) { data.PutExtra("canceled", "true"); } @@ -734,6 +734,11 @@ namespace Bit.Droid.Services return Accessibility.AccessibilityHelpers.OverlayPermitted(); } + public bool HasAutofillService() + { + return true; + } + public void OpenAccessibilityOverlayPermissionSettings() { var activity = (MainActivity)CrossCurrentActivity.Current.Activity; diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs index ad267f7ab..350f791db 100644 --- a/src/App/Abstractions/IDeviceActionService.cs +++ b/src/App/Abstractions/IDeviceActionService.cs @@ -35,6 +35,7 @@ namespace Bit.App.Abstractions void Background(); bool AutofillAccessibilityServiceRunning(); bool AutofillAccessibilityOverlayPermitted(); + bool HasAutofillService(); bool AutofillServiceEnabled(); void DisableAutofillService(); bool AutofillServicesEnabled(); diff --git a/src/App/Pages/Settings/AutofillServicesPageViewModel.cs b/src/App/Pages/Settings/AutofillServicesPageViewModel.cs index ecd89b6af..52b3cf4c3 100644 --- a/src/App/Pages/Settings/AutofillServicesPageViewModel.cs +++ b/src/App/Pages/Settings/AutofillServicesPageViewModel.cs @@ -192,7 +192,8 @@ namespace Bit.App.Pages public void UpdateEnabled() { - AutofillServiceToggled = _deviceActionService.AutofillServiceEnabled(); + AutofillServiceToggled = + _deviceActionService.HasAutofillService() && _deviceActionService.AutofillServiceEnabled(); AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning(); DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted(); } diff --git a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs index b90a144a2..360b5d225 100644 --- a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs +++ b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs @@ -66,9 +66,9 @@ namespace Bit.App.Pages public void Init(AppOptions appOptions) { _appOptions = appOptions; - Uri = appOptions.Uri; + Uri = appOptions?.Uri; string name = null; - if (Uri.StartsWith(Constants.AndroidAppProtocol)) + if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false) { name = Uri.Substring(Constants.AndroidAppProtocol.Length); } diff --git a/src/Core/Services/TokenService.cs b/src/Core/Services/TokenService.cs index c30d33479..dcf3ad17e 100644 --- a/src/Core/Services/TokenService.cs +++ b/src/Core/Services/TokenService.cs @@ -208,10 +208,10 @@ namespace Bit.Core.Services if (_accessTokenForDecoding == null) { await GetTokenAsync(); - } - if (_accessTokenForDecoding == null) - { - return false; + if (_accessTokenForDecoding == null) + { + return false; + } } var decoded = DecodeToken(); if (decoded?["amr"] == null) diff --git a/src/iOS.Core/Services/DeviceActionService.cs b/src/iOS.Core/Services/DeviceActionService.cs index 5699e5f9b..28b12703a 100644 --- a/src/iOS.Core/Services/DeviceActionService.cs +++ b/src/iOS.Core/Services/DeviceActionService.cs @@ -392,6 +392,11 @@ namespace Bit.iOS.Core.Services throw new NotImplementedException(); } + public bool HasAutofillService() + { + return false; + } + public bool AutofillServiceEnabled() { throw new NotImplementedException(); From b6ad3527db9b6c247fc1a4b07041dc5dac3cf892 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 11:52:21 +0200 Subject: [PATCH 067/100] Autosync the updated translations (#1877) Co-authored-by: github-actions <> --- src/App/Resources/AppResources.el.resx | 6 +- src/App/Resources/AppResources.he.resx | 81 +++++++++++++------------- src/App/Resources/AppResources.ko.resx | 6 +- src/App/Resources/AppResources.pl.resx | 8 +-- src/App/Resources/AppResources.ru.resx | 2 +- src/App/Resources/AppResources.sk.resx | 2 +- 6 files changed, 53 insertions(+), 52 deletions(-) diff --git a/src/App/Resources/AppResources.el.resx b/src/App/Resources/AppResources.el.resx index b891cbb83..554bcb0a6 100644 --- a/src/App/Resources/AppResources.el.resx +++ b/src/App/Resources/AppResources.el.resx @@ -2121,13 +2121,13 @@ Μετάβαση στον επόμενο διαθέσιμο λογαριασμό - Account Locked + Κλειδωμένος Λογαριασμός - Account logged out successfully + Ο λογαριασμός αποσυνδέθηκε επιτυχώς - Account removed successfully + Ο λογαριασμός αφαιρέθηκε επιτυχώς Διαγραφή Λογαριασμού diff --git a/src/App/Resources/AppResources.he.resx b/src/App/Resources/AppResources.he.resx index 398589077..fa6ee3939 100644 --- a/src/App/Resources/AppResources.he.resx +++ b/src/App/Resources/AppResources.he.resx @@ -276,16 +276,16 @@ האם אתה בטוח שברצונך להתנתק? - Remove Account + הסר/י חשבון - Are you sure you want to remove this account? + האם את/ה בטוח/ה שברצונך להסיר חשבון זה? - Account Already Added + החשבון כבר נוסף - Would you like to switch to it now? + האם תרצה לעבור אליו עכשיו? סיסמה ראשית @@ -1073,7 +1073,7 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". שם משפחה - Full Name + שם מלא מספר רשיון @@ -1200,7 +1200,7 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". מוסתר - Linked + מקושר טקסט @@ -1472,7 +1472,7 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". בטל נעילה - Unlock Vault + פתח נעילת כספת 30 דקות @@ -1491,7 +1491,7 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". הכספת שלך נעולה. הזן את קוד הPIN שלך כדי להמשיך. - Your vault is locked. Verify your identity to continue. + הכספת שלך נעולה. אמת את זהותך כדי להמשיך. כהה @@ -1634,13 +1634,13 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". הזן את הסיסמה הראשית שלך עבור יצוא המידע מהכספת. - Send a verification code to your email + שליחת קוד אימות לדוא״ל שלך - Code Sent! + קוד נשלח! - Confirm your identity to continue. + אשר את זהותך כדי להמשיך. הקובץ מכיל את פרטי הכספת שלך בפורמט לא מוצפן. מומלץ להעביר את הקובץ רק בדרכים מוצפנות, ומאוד לא מומלץ לשמור או לשלוח את הקובץ הזה בדרכים לא מוצפנות (כדוגמת סתם אימייל). מחק את הקובץ מיד לאחר שסיימת את השימוש בו. @@ -2066,19 +2066,19 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". כרגע לא ניתן לעדכן את הסיסמה - Remove Master Password + הסרת סיסמה ראשית - {0} is using SSO with customer-managed encryption. Continuing will remove your Master Password from your account and require SSO to login. + {0} משתמש/ת ב־SSO עם הצפנה בניהול לקוחות. המשך התהליך יסיר את סיסמת האב שלך מחשבונך וידרוש SSO כדי להתחבר. - If you do not want to remove your Master Password, you may leave this organization. + אם אינך רוצה להסיר את הסיסמה הראשית שלך, תוכל לעזוב את הארגון הזה. - Leave Organization + עזוב ארגון - Leave {0}? + לעזוב את {0}? אימות אינטרנט באמצעות FIDO2 @@ -2112,75 +2112,76 @@ Bitwarden בעזרת פתיחת חלון "הגדרות". מדיניות אחת או יותר של הארגון שלך מונעות ממך לייצא את הכספת האישית שלך. - Add Account + הוספת חשבון - Unlocked + פתוח - Locked + נעול - Logged Out + בוצעה יציאה - Switched to next available account + הוחלף לחשבון הזמין הבא - Account Locked + החשבון נעול - Account logged out successfully + נותק בהצלחה מהחשבון - Account removed successfully + חשבון נמחק בהצלחה - Delete Account + מחק חשבון - Deleting your account is permanent + מחיקת חשבונך היא לצמיתות - Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue? + החשבון שלך וכל הנתונים הקשורים יימחקו ולא ניתן יהיה לשחזרם. האם את/ה בטוח/ה שאת/ה רוצה להמשיך? + - Deleting your account + מוחק את החשבון שלך - Your account has been permanently deleted + חשבונך נמחק לצמיתות - Invalid Verification Code. + קוד אימות שגוי. - Request one-time password + בקשת סיסמה חד־פעמית - Send Code + שליחת קוד - Sending + בשליחה - Copy Send link on save + העתק קישור שליחה בעת שמירה - Sending code + שליחת קוד - Verifying + אימות - Resend Code + שליחת קוד בשנית - A verification code was sent to your email + קוד אימות נשלח לדוא"ל שלך - An error occurred while sending a verification code to your email. Please try again + אירעה שגיאה בעת שליחת קוד אימות לדוא"ל שלך. בבקשה נסה שוב - Enter the verification code that was sent to your email + הזן את קוד האימות שנשלח לדוא"ל שלך diff --git a/src/App/Resources/AppResources.ko.resx b/src/App/Resources/AppResources.ko.resx index d30696d7a..a552de8d0 100644 --- a/src/App/Resources/AppResources.ko.resx +++ b/src/App/Resources/AppResources.ko.resx @@ -2120,13 +2120,13 @@ 사용 가능한 다음 계정으로 전환함 - Account Locked + 계정 잠김 - Account logged out successfully + 계정이 성공적으로 로그아웃되었습니다 - Account removed successfully + 계정이 성공적으로 제거되었습니다 계정 삭제 diff --git a/src/App/Resources/AppResources.pl.resx b/src/App/Resources/AppResources.pl.resx index 8795a97da..be4d55593 100644 --- a/src/App/Resources/AppResources.pl.resx +++ b/src/App/Resources/AppResources.pl.resx @@ -285,7 +285,7 @@ Konto zostało już dodane - Czy chcesz przełączyć się na nie teraz? + Czy chcesz przełączyć się teraz? Hasło główne @@ -1162,10 +1162,10 @@ Usługa ułatwienia dostępu - Usługa autouzupełniania Bitwarden wykorzystuje funkcję Androida, aby wypełniać dane logowania, kart kredytowych i osobistych informacji w aplikacjach na Twoim urządzeniu. + Usługa autouzupełniania Bitwarden wykorzystuje funkcję Androida, aby wypełnić dane logowania w innych aplikacjach na Twoim urządzeniu. - Użyj usługi autouzupełniania Bitwarden, aby wypełnić dane logowania, dane kart kredytowych i informacje osobiste w innych aplikacjach. + Użyj usługi autouzupełniania Bitwarden, aby wypełnić dane logowania w innych aplikacjach. Otwórz ustawienia autouzupełniania @@ -1299,7 +1299,7 @@ 1. przejdź do Ustawień systemu iOS - 2. Dotknij "Hasła i konta" + 2. Dotknij "Hasła" 3. Dotknij opcji "Usługa autouzupełniania" diff --git a/src/App/Resources/AppResources.ru.resx b/src/App/Resources/AppResources.ru.resx index 851131357..ed1206497 100644 --- a/src/App/Resources/AppResources.ru.resx +++ b/src/App/Resources/AppResources.ru.resx @@ -1299,7 +1299,7 @@ 1. Перейдите в 'Настройки' iOS - 2. Нажмите на пункт "Пароли и учетные записи" + 2. Нажмите 'Пароли' 3. Нажмите 'Автозаполнение паролей' diff --git a/src/App/Resources/AppResources.sk.resx b/src/App/Resources/AppResources.sk.resx index 24e8daa08..068ca8160 100644 --- a/src/App/Resources/AppResources.sk.resx +++ b/src/App/Resources/AppResources.sk.resx @@ -1299,7 +1299,7 @@ 1. Prejdite do aplikácie "Nastavenia" pre iOS - 2. Ťuknite na položku "Heslá & účty" + 2. Ťuknite na položku "Heslá" 3. Klepnite na možnosť "Automaticky vypĺňať heslá" From 14d4b2ee297362da5dc082248a099dd70e4d5e26 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Sun, 10 Apr 2022 13:00:52 -0400 Subject: [PATCH 068/100] [BEEEP] add context to search titles (#1878) * add more descriptive titles to search pages * add App Resources --- .../SendGroupingsPage.xaml.cs | 2 +- src/App/Pages/Send/SendsPage.xaml.cs | 15 +++++++++++--- src/App/Pages/Vault/CiphersPage.xaml.cs | 20 ++++--------------- .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 3 +-- src/App/Resources/AppResources.Designer.cs | 14 +++++++++---- src/App/Resources/AppResources.resx | 14 ++++++++----- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs index d15c5e21c..7bdf38b7a 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs @@ -160,7 +160,7 @@ namespace Bit.App.Pages { if (DoOnce()) { - var page = new SendsPage(_vm.Filter, _vm.Type != null); + var page = new SendsPage(_vm.Filter, _vm.Type); await Navigation.PushModalAsync(new NavigationPage(page)); } } diff --git a/src/App/Pages/Send/SendsPage.xaml.cs b/src/App/Pages/Send/SendsPage.xaml.cs index ffffdfd6a..73d2fb3ec 100644 --- a/src/App/Pages/Send/SendsPage.xaml.cs +++ b/src/App/Pages/Send/SendsPage.xaml.cs @@ -2,6 +2,7 @@ using System.Linq; using Bit.App.Controls; using Bit.App.Resources; +using Bit.Core.Enums; using Bit.Core.Models.View; using Xamarin.Forms; @@ -12,15 +13,22 @@ namespace Bit.App.Pages private SendsPageViewModel _vm; private bool _hasFocused; - public SendsPage(Func filter, bool type = false) + public SendsPage(Func filter, SendType? type = null) { InitializeComponent(); _vm = BindingContext as SendsPageViewModel; _vm.Page = this; _vm.Filter = filter; - if (type) + if (type != null) { - _vm.PageTitle = AppResources.SearchType; + if (type == SendType.File) + { + _vm.PageTitle = AppResources.SearchFileSends; + } + else if (type == SendType.Text) + { + _vm.PageTitle = AppResources.SearchTextSends; + } } else { @@ -33,6 +41,7 @@ namespace Bit.App.Pages _searchBar.Placeholder = AppResources.Search; _mainLayout.Children.Insert(0, _searchBar); _mainLayout.Children.Insert(1, _separator); + ShowModalAnimationDelay = 0; } else { diff --git a/src/App/Pages/Vault/CiphersPage.xaml.cs b/src/App/Pages/Vault/CiphersPage.xaml.cs index c29ad69ea..c730eb5ea 100644 --- a/src/App/Pages/Vault/CiphersPage.xaml.cs +++ b/src/App/Pages/Vault/CiphersPage.xaml.cs @@ -17,8 +17,7 @@ namespace Bit.App.Pages private CiphersPageViewModel _vm; private bool _hasFocused; - public CiphersPage(Func filter, bool folder = false, bool collection = false, - bool type = false, string autofillUrl = null, bool deleted = false) + public CiphersPage(Func filter, string pageTitle = null, string autofillUrl = null, bool deleted = false) { InitializeComponent(); _vm = BindingContext as CiphersPageViewModel; @@ -26,21 +25,9 @@ namespace Bit.App.Pages _vm.Filter = filter; _vm.AutofillUrl = _autofillUrl = autofillUrl; _vm.Deleted = deleted; - if (deleted) + if (pageTitle != null) { - _vm.PageTitle = AppResources.SearchTrash; - } - else if (folder) - { - _vm.PageTitle = AppResources.SearchFolder; - } - else if (collection) - { - _vm.PageTitle = AppResources.SearchCollection; - } - else if (type) - { - _vm.PageTitle = AppResources.SearchType; + _vm.PageTitle = string.Format(AppResources.SearchGroup, pageTitle); } else { @@ -53,6 +40,7 @@ namespace Bit.App.Pages _searchBar.Placeholder = AppResources.Search; _mainLayout.Children.Insert(0, _searchBar); _mainLayout.Children.Insert(1, _separator); + ShowModalAnimationDelay = 0; } else { diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 7c806a82e..395dd8cf2 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -225,8 +225,7 @@ namespace Bit.App.Pages await _accountListOverlay.HideAsync(); if (DoOnce()) { - var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null, - _vm.Type != null, deleted: _vm.Deleted); + var page = new CiphersPage(_vm.Filter, _vm.MainPage ? null : _vm.PageTitle, deleted: _vm.Deleted); await Navigation.PushModalAsync(new NavigationPage(page)); } } diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index dbcd33416..def2096b2 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -2387,15 +2387,21 @@ namespace Bit.App.Resources { } } - public static string SearchFolder { + public static string SearchFileSends { get { - return ResourceManager.GetString("SearchFolder", resourceCulture); + return ResourceManager.GetString("SearchFileSends", resourceCulture); } } - public static string SearchType { + public static string SearchTextSends { get { - return ResourceManager.GetString("SearchType", resourceCulture); + return ResourceManager.GetString("SearchTextSends", resourceCulture); + } + } + + public static string SearchGroup { + get { + return ResourceManager.GetString("SearchGroup", resourceCulture); } } diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index bd7f76cee..cda5cb54b 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -659,7 +659,7 @@ Re-type Master Password - Search vault + Search Vault Security @@ -1372,11 +1372,15 @@ Search collection - - Search folder + + Search File Sends - - Search type + + Search Text Sends + + + Search {0} + ex: Search Logins Type From cfbbea59e9a687a7d5bcc1da1d70acf85e1a45cf Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Tue, 19 Apr 2022 20:38:17 -0400 Subject: [PATCH 069/100] account switching in autofill UI (Android) (#1882) --- src/App/App.xaml.cs | 17 ++++++- src/App/Pages/Vault/AutofillCiphersPage.xaml | 25 +++++++++- .../Pages/Vault/AutofillCiphersPage.xaml.cs | 48 +++++++++++++++++-- .../Vault/AutofillCiphersPageViewModel.cs | 26 ++++++++-- 4 files changed, 106 insertions(+), 10 deletions(-) diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index f51974df2..8391cbf4b 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -208,7 +208,10 @@ namespace Bit.App { await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); } - SetTabsPageFromAutofill(isLocked); + if (!SetTabsPageFromAutofill(isLocked)) + { + ClearAutofillUri(); + } await SleptAsync(); } } @@ -365,7 +368,15 @@ namespace Bit.App } } - private void SetTabsPageFromAutofill(bool isLocked) + private void ClearAutofillUri() + { + if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri)) + { + Options.Uri = null; + } + } + + private bool SetTabsPageFromAutofill(bool isLocked) { if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) && !Options.FromAutofillFramework) @@ -385,7 +396,9 @@ namespace Bit.App } }); }); + return true; } + return false; } private void Prime() diff --git a/src/App/Pages/Vault/AutofillCiphersPage.xaml b/src/App/Pages/Vault/AutofillCiphersPage.xaml index 5bb8b4004..4a99f7b0e 100644 --- a/src/App/Pages/Vault/AutofillCiphersPage.xaml +++ b/src/App/Pages/Vault/AutofillCiphersPage.xaml @@ -15,7 +15,18 @@ - + + @@ -58,7 +69,7 @@ VerticalOptions="CenterAndExpand" Padding="20, 0" Spacing="20" - IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"> + IsVisible="{Binding ShowNoData}"> @@ -88,6 +99,8 @@ AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0, 0, 1, 1"> + + + + diff --git a/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs b/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs index 69d04cda5..3c680aa05 100644 --- a/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs +++ b/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs @@ -1,5 +1,4 @@ using Bit.App.Models; -using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; @@ -15,7 +14,8 @@ namespace Bit.App.Pages public partial class AutofillCiphersPage : BaseContentPage { private readonly AppOptions _appOptions; - private readonly IPlatformUtilsService _platformUtilsService; + private readonly IBroadcasterService _broadcasterService; + private readonly ISyncService _syncService; private readonly IVaultTimeoutService _vaultTimeoutService; private AutofillCiphersPageViewModel _vm; @@ -24,17 +24,23 @@ namespace Bit.App.Pages { _appOptions = appOptions; InitializeComponent(); + SetActivityIndicator(_mainContent); _vm = BindingContext as AutofillCiphersPageViewModel; _vm.Page = this; _vm.Init(appOptions); - _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + _broadcasterService = ServiceContainer.Resolve("broadcasterService"); + _syncService = ServiceContainer.Resolve("syncService"); _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService"); } protected async override void OnAppearing() { base.OnAppearing(); + if (_syncService.SyncInProgress) + { + IsBusy = true; + } if (!await AppHelpers.IsVaultTimeoutImmediateAsync()) { await _vaultTimeoutService.CheckVaultTimeoutAsync(); @@ -43,6 +49,30 @@ namespace Bit.App.Pages { return; } + + _accountAvatar?.OnAppearing(); + _vm.AvatarImageSource = await GetAvatarImageSourceAsync(); + + _broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) => + { + if (message.Command == "syncStarted") + { + Device.BeginInvokeOnMainThread(() => IsBusy = true); + } + else if (message.Command == "syncCompleted") + { + await Task.Delay(500); + Device.BeginInvokeOnMainThread(() => + { + IsBusy = false; + if (_vm.LoadedOnce) + { + var task = _vm.LoadAsync(); + } + }); + } + }); + await LoadOnAppearedAsync(_mainLayout, false, async () => { try @@ -59,6 +89,11 @@ namespace Bit.App.Pages protected override bool OnBackButtonPressed() { + if (_accountListOverlay.IsVisible) + { + _accountListOverlay.HideAsync().FireAndForget(); + return true; + } if (Device.RuntimePlatform == Device.Android) { _appOptions.Uri = null; @@ -66,6 +101,13 @@ namespace Bit.App.Pages return base.OnBackButtonPressed(); } + protected override void OnDisappearing() + { + base.OnDisappearing(); + IsBusy = false; + _accountAvatar?.OnDisappearing(); + } + private async void RowSelected(object sender, SelectionChangedEventArgs e) { ((ExtendedCollectionView)sender).SelectedItem = null; diff --git a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs index 360b5d225..77e0dea0a 100644 --- a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs +++ b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xamarin.CommunityToolkit.ObjectModel; +using Bit.App.Controls; using Xamarin.Forms; namespace Bit.App.Pages @@ -23,8 +24,10 @@ namespace Bit.App.Pages private readonly ICipherService _cipherService; private readonly IStateService _stateService; private readonly IPasswordRepromptService _passwordRepromptService; + private readonly IMessagingService _messagingService; + private readonly ILogger _logger; - private AppOptions _appOptions; + private bool _showNoData; private bool _showList; private string _noDataText; private bool _websiteIconsEnabled; @@ -36,15 +39,30 @@ namespace Bit.App.Pages _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _stateService = ServiceContainer.Resolve("stateService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); + _messagingService = ServiceContainer.Resolve("messagingService"); + _logger = ServiceContainer.Resolve("logger"); GroupedItems = new ObservableRangeCollection(); CipherOptionsCommand = new Command(CipherOptionsAsync); + + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) + { + AllowAddAccountRow = false + }; } public string Name { get; set; } public string Uri { get; set; } public Command CipherOptionsCommand { get; set; } + public bool LoadedOnce { get; set; } public ObservableRangeCollection GroupedItems { get; set; } + public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } + + public bool ShowNoData + { + get => _showNoData; + set => SetProperty(ref _showNoData, value); + } public bool ShowList { @@ -65,7 +83,6 @@ namespace Bit.App.Pages public void Init(AppOptions appOptions) { - _appOptions = appOptions; Uri = appOptions?.Uri; string name = null; if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false) @@ -87,8 +104,10 @@ namespace Bit.App.Pages public async Task LoadAsync() { - WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); + LoadedOnce = true; ShowList = false; + ShowNoData = false; + WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); var groupedItems = new List(); var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null); var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList(); @@ -150,6 +169,7 @@ namespace Bit.App.Pages } } ShowList = groupedItems.Any(); + ShowNoData = !ShowList; } public async Task SelectCipherAsync(CipherView cipher, bool fuzzy) From 35853a3815047513473feb3ac93ecfa4fea70554 Mon Sep 17 00:00:00 2001 From: dwbit <98768076+dwbit@users.noreply.github.com> Date: Wed, 20 Apr 2022 18:25:37 -0400 Subject: [PATCH 070/100] Contribution Documentation edits (#1880) * Update crowdin manager and forum category Updating Crowdin contact from Kyle to dwbit. Update 'User-to-User Support' forum category to 'Ask the Bitwarden Community' * Text corrections Correcting title of forum category * Add 'category' to text change Update the 'Ask the Bitwarden Community' text change. --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a5f1e910..baeeae35e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Here is how you can get involved: * **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help) -* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums +* **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums * **Translate:** See the localization (i10n) section below @@ -35,6 +35,6 @@ We use a translation tool called [Crowdin](https://crowdin.com) to help manage o If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile -If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin). +If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit). You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/ From 80bd8ba9d1497445b87837497788412d8ce13d9b Mon Sep 17 00:00:00 2001 From: dwbit <98768076+dwbit@users.noreply.github.com> Date: Thu, 21 Apr 2022 08:03:21 -0400 Subject: [PATCH 071/100] Change source string 'copy notes' to 'copy note' (#1881) Change the value for 'copy notes' to 'copy note' since it is applying a single action on 1 item. This source string is already 'copy note' in the browser extension. --- src/App/Resources/AppResources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index cda5cb54b..1c0448e6a 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1542,7 +1542,7 @@ Default (System) - Copy Notes + Copy Note Exit From ab6dde4a11889a7755be638fcb3ec224bd7d338d Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Thu, 21 Apr 2022 21:29:47 -0400 Subject: [PATCH 072/100] update XF and revert old workarounds (#1885) --- src/Android/Android.csproj | 2 +- src/Android/Services/BiometricService.cs | 14 ++++++--- src/App/App.csproj | 6 ++-- src/App/Pages/Send/SendAddEditPage.xaml | 9 +++--- src/App/Pages/Send/SendAddEditPage.xaml.cs | 7 ++--- src/App/Pages/Vault/AddEditPage.xaml | 1 - src/App/Pages/Vault/AddEditPage.xaml.cs | 2 -- src/App/Pages/Vault/AddEditPageViewModel.cs | 23 ++------------- src/Core/Services/StateMigrationService.cs | 29 ------------------- .../iOS.ShareExtension.csproj | 2 +- src/iOS/iOS.csproj | 2 +- 11 files changed, 26 insertions(+), 71 deletions(-) diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index bd3ef91fa..f04d1acf0 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -84,7 +84,7 @@ - 1.7.1 + 1.7.2 122.0.0 diff --git a/src/Android/Services/BiometricService.cs b/src/Android/Services/BiometricService.cs index c55673f19..ee717eb14 100644 --- a/src/Android/Services/BiometricService.cs +++ b/src/Android/Services/BiometricService.cs @@ -1,13 +1,13 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Android.OS; using Android.Security.Keystore; using Bit.Core.Abstractions; using Java.Security; using Javax.Crypto; +#if !FDROID +using Microsoft.AppCenter.Crashes; +#endif namespace Bit.Droid.Services { @@ -74,6 +74,9 @@ namespace Bit.Droid.Services catch (InvalidKeyException e) { // Fallback for old bitwarden users without a key +#if !FDROID + Crashes.TrackError(e); +#endif CreateKey(); } @@ -94,10 +97,13 @@ namespace Bit.Droid.Services keyGen.Init(keyGenSpec); keyGen.GenerateKey(); } - catch + catch (Exception e) { // Catch silently to allow biometrics to function on devices that are in a state where key generation // is not functioning +#if !FDROID + Crashes.TrackError(e); +#endif } } } diff --git a/src/App/App.csproj b/src/App/App.csproj index 8894ca5ae..60e989699 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -16,10 +16,10 @@ - - + + - + diff --git a/src/App/Pages/Send/SendAddEditPage.xaml b/src/App/Pages/Send/SendAddEditPage.xaml index b72250979..fc3009e07 100644 --- a/src/App/Pages/Send/SendAddEditPage.xaml +++ b/src/App/Pages/Send/SendAddEditPage.xaml @@ -256,23 +256,24 @@ x:Name="_btnOptions" StyleClass="box-row-button" TextColor="{DynamicResource PrimaryColor}" - Margin="0" /> + Margin="0" + Clicked="ToggleOptions_Clicked"/> + IsVisible="{Binding ShowOptions}" /> + IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" /> - + diff --git a/src/App/Pages/Send/SendAddEditPage.xaml.cs b/src/App/Pages/Send/SendAddEditPage.xaml.cs index 3121acf0e..0f4ba6f4e 100644 --- a/src/App/Pages/Send/SendAddEditPage.xaml.cs +++ b/src/App/Pages/Send/SendAddEditPage.xaml.cs @@ -53,10 +53,9 @@ namespace Bit.App.Pages _vm.SegmentedButtonFontSize = 13; _vm.SegmentedButtonMargins = new Thickness(0, 10, 0, 0); _vm.EditorMargins = new Thickness(0, 5, 0, 0); - // Review this when https://github.com/bitwarden/mobile/pull/1454 workaround can be reverted - //_btnOptions.WidthRequest = 70; - //_btnOptionsDown.WidthRequest = 30; - //_btnOptionsUp.WidthRequest = 30; + _btnOptions.WidthRequest = 70; + _btnOptionsDown.WidthRequest = 30; + _btnOptionsUp.WidthRequest = 30; } else if (Device.RuntimePlatform == Device.iOS) { diff --git a/src/App/Pages/Vault/AddEditPage.xaml b/src/App/Pages/Vault/AddEditPage.xaml index 056214206..3e8c85ee2 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml +++ b/src/App/Pages/Vault/AddEditPage.xaml @@ -732,7 +732,6 @@ diff --git a/src/App/Pages/Vault/AddEditPage.xaml.cs b/src/App/Pages/Vault/AddEditPage.xaml.cs index 0fd9be75a..796866d8b 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml.cs +++ b/src/App/Pages/Vault/AddEditPage.xaml.cs @@ -51,7 +51,6 @@ namespace Bit.App.Pages _vm.CipherId = cipherId; _vm.FolderId = folderId == "none" ? null : folderId; _vm.CollectionIds = collectionId != null ? new HashSet(new List { collectionId }) : null; - _vm.CollectionsRepeaterView = _collectionsRepeaterView; _vm.Type = type; _vm.DefaultName = name ?? appOptions?.SaveName; _vm.DefaultUri = uri ?? appOptions?.Uri; @@ -171,7 +170,6 @@ namespace Bit.App.Pages { RequestFocus(_nameEntry); } - _scrollView.Scrolled += (sender, args) => _vm.HandleScroll(); }); // Hide password reprompt option if using key connector _passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector(); diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs index f71c7ced9..40fd047f5 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/AddEditPageViewModel.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Bit.App.Abstractions; -using Bit.App.Controls; using Bit.App.Models; using Bit.App.Resources; using Bit.Core; @@ -44,7 +43,6 @@ namespace Bit.App.Pages private int _ownershipSelectedIndex; private bool _hasCollections; private string _previousCipherId; - private DateTime _lastHandledScrollTime; private List _writeableCollections; private string[] _additionalCipherProperties = new string[] { @@ -168,7 +166,7 @@ namespace Bit.App.Pages public ExtendedObservableCollection Uris { get; set; } public ExtendedObservableCollection Fields { get; set; } public ExtendedObservableCollection Collections { get; set; } - public RepeaterView CollectionsRepeaterView { get; set; } + public int TypeSelectedIndex { get => _typeSelectedIndex; @@ -821,30 +819,13 @@ namespace Bit.App.Pages { var cols = _writeableCollections.Where(c => c.OrganizationId == Cipher.OrganizationId) .Select(c => new CollectionViewModel { Collection = c }).ToList(); - HasCollections = cols.Any(); Collections.ResetWithRange(cols); - Collections = new ExtendedObservableCollection(cols); } else { - HasCollections = false; Collections.ResetWithRange(new List()); - Collections = new ExtendedObservableCollection(new List()); } - } - - public void HandleScroll() - { - // workaround for https://github.com/xamarin/Xamarin.Forms/issues/13607 - // required for org ownership/collections to render properly in XF4.5+ - if (!HasCollections || - EditMode || - (DateTime.Now - _lastHandledScrollTime < TimeSpan.FromMilliseconds(200))) - { - return; - } - CollectionsRepeaterView.ItemsSource = Collections; - _lastHandledScrollTime = DateTime.Now; + HasCollections = Collections.Any(); } private void TriggerCipherChanged() diff --git a/src/Core/Services/StateMigrationService.cs b/src/Core/Services/StateMigrationService.cs index 91f74912c..209493d47 100644 --- a/src/Core/Services/StateMigrationService.cs +++ b/src/Core/Services/StateMigrationService.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Utilities; -using Newtonsoft.Json; namespace Bit.Core.Services { @@ -354,7 +352,6 @@ namespace Bit.Core.Services private async Task GetValueAsync(Storage storage, string key) { var value = await GetStorageService(storage).GetAsync(key); - Log("GET", storage, key, JsonConvert.SerializeObject(value)); return value; } @@ -365,13 +362,11 @@ namespace Bit.Core.Services await RemoveValueAsync(storage, key); return; } - Log("SET", storage, key, JsonConvert.SerializeObject(value)); await GetStorageService(storage).SaveAsync(key, value); } private async Task RemoveValueAsync(Storage storage, string key) { - Log("REMOVE", storage, key, null); await GetStorageService(storage).RemoveAsync(key); } @@ -387,29 +382,5 @@ namespace Bit.Core.Services return _liteDbStorageService; } } - - private void Log(string tag, Storage storage, string key, string value) - { - // TODO Remove this once all bugs are squished - string text; - switch (storage) - { - case Storage.Secure: - text = "SECURE / "; - break; - case Storage.Prefs: - text = "PREFS / "; - break; - default: - text = "LITEDB / "; - break; - } - text += "Key: " + key + " / "; - if (value != null) - { - text += "Value: " + value; - } - Debug.WriteLine(text, ">>> " + tag); - } } } diff --git a/src/iOS.ShareExtension/iOS.ShareExtension.csproj b/src/iOS.ShareExtension/iOS.ShareExtension.csproj index 03e320315..124867571 100644 --- a/src/iOS.ShareExtension/iOS.ShareExtension.csproj +++ b/src/iOS.ShareExtension/iOS.ShareExtension.csproj @@ -167,7 +167,7 @@ - + 4.4.0 diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj index e7f830539..66c0d0b2d 100644 --- a/src/iOS/iOS.csproj +++ b/src/iOS/iOS.csproj @@ -183,7 +183,7 @@ - 1.7.1 + 1.7.2 From 99828c7ead78f5613004e3e65b317c3b66ec3aa0 Mon Sep 17 00:00:00 2001 From: sneakernuts <671942+sneakernuts@users.noreply.github.com> Date: Fri, 22 Apr 2022 07:35:19 -0600 Subject: [PATCH 073/100] Switched org (#1887) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 265a8a892..b755a43f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -544,7 +544,7 @@ jobs: || github.ref == 'refs/heads/hotfix-rc' env: APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} - run: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN + run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN shell: bash - name: Deploy to App Store From 2cab62fda5c1106cca2852f78e04ea4c40521dcb Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Mon, 25 Apr 2022 16:09:47 -0300 Subject: [PATCH 074/100] TDL-13 Removed workaround of null reference on LabelRenderer given that Xamarin forms has been updated with the fix (#1889) --- src/iOS.Core/Renderers/CustomLabelRenderer.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/iOS.Core/Renderers/CustomLabelRenderer.cs b/src/iOS.Core/Renderers/CustomLabelRenderer.cs index f1ddc7e9e..9daf4fa52 100644 --- a/src/iOS.Core/Renderers/CustomLabelRenderer.cs +++ b/src/iOS.Core/Renderers/CustomLabelRenderer.cs @@ -23,18 +23,7 @@ namespace Bit.iOS.Core.Renderers protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { - try - { - base.OnElementPropertyChanged(sender, e); - } - catch (NullReferenceException) - { - // Do nothing... - // There is an issue on Xamarin Forms which throws a null reference - // https://appcenter.ms/users/kspearrin/apps/bitwarden/crashes/errors/534094859u/overview - // TODO: Maybe something like this https://github.com/matteobortolazzo/HtmlLabelPlugin/pull/113 can be implemented to avoid this - // on html labels. - } + base.OnElementPropertyChanged(sender, e); if (e.PropertyName == Label.FontProperty.PropertyName || e.PropertyName == Label.TextProperty.PropertyName || From 57213607a73f348d7a0e842b5145e439b62c1d12 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Mon, 25 Apr 2022 18:43:55 -0300 Subject: [PATCH 075/100] PS-291 Fix password history to update the collection on the main thread to load correctly (#1890) --- src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs b/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs index ab63c81b0..1d34812ec 100644 --- a/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs +++ b/src/App/Pages/Generator/GeneratorHistoryPageViewModel.cs @@ -41,8 +41,11 @@ namespace Bit.App.Pages public async Task InitAsync() { var history = await _passwordGenerationService.GetHistoryAsync(); - History.ResetWithRange(history ?? new List()); - ShowNoData = History.Count == 0; + Device.BeginInvokeOnMainThread(() => + { + History.ResetWithRange(history ?? new List()); + ShowNoData = History.Count == 0; + }); } public async Task ClearAsync() From e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 26 Apr 2022 17:21:07 +0200 Subject: [PATCH 076/100] Add dotnet-format tool (#1737) --- .config/dotnet-tools.json | 12 ++++++++++++ .editorconfig | 1 + .github/workflows/build.yml | 8 ++++++++ 3 files changed, 21 insertions(+) create mode 100644 .config/dotnet-tools.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..7f35d2cb1 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-format": { + "version": "5.1.250801", + "commands": [ + "dotnet-format" + ] + } + } +} diff --git a/.editorconfig b/.editorconfig index 1fbf4e5d8..1d09080d9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,7 @@ root = true # Don't use tabs for indentation. [*] indent_style = space +end_of_line = lf # (Please don't specify an indent_size here; that has too many unintended consequences.) # Code files diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b755a43f1..4f9ae9888 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,6 +105,14 @@ jobs: - name: Restore packages run: nuget restore + - name: Restore tools + run: dotnet tool restore + shell: pwsh + + # - name: Verify Format + # run: dotnet tool run dotnet-format --check + # shell: pwsh + - name: Run Core tests run: dotnet test test/Core.Test/Core.Test.csproj From 04539af2a66668b6e85476d5cf318c9150ec4357 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 26 Apr 2022 17:21:17 +0200 Subject: [PATCH 077/100] Run dotnet format (#1738) --- src/App/Abstractions/IDeviceActionService.cs | 4 +- .../Abstractions/IPasswordRepromptService.cs | 2 +- .../IPushNotificationListenerService.cs | 4 +- src/App/App.xaml.cs | 10 +- .../AccountSwitchingOverlayView.xaml.cs | 2 +- .../AccountSwitchingOverlayViewModel.cs | 4 +- .../AccountViewCell/AccountViewCell.xaml.cs | 2 +- src/App/Controls/AvatarImageSource.cs | 4 +- src/App/Controls/ExtendedStepper.cs | 4 +- .../SendViewCell/SendViewCell.xaml.cs | 4 +- src/App/Effects/FabShadowEffect.cs | 4 +- .../Accounts/BaseChangePasswordViewModel.cs | 14 +-- .../Pages/Accounts/EnvironmentPage.xaml.cs | 6 +- .../Accounts/EnvironmentPageViewModel.cs | 6 +- src/App/Pages/Accounts/HintPageViewModel.cs | 4 +- src/App/Pages/Accounts/HomePage.xaml.cs | 6 +- src/App/Pages/Accounts/LockPageViewModel.cs | 2 +- src/App/Pages/Accounts/LoginPage.xaml.cs | 2 +- src/App/Pages/Accounts/LoginPageViewModel.cs | 2 +- src/App/Pages/Accounts/LoginSsoPage.xaml.cs | 10 +- .../Pages/Accounts/LoginSsoPageViewModel.cs | 12 +-- src/App/Pages/Accounts/RegisterPage.xaml.cs | 2 +- .../Pages/Accounts/RegisterPageViewModel.cs | 24 ++--- .../Accounts/SetPasswordPageViewModel.cs | 20 ++-- src/App/Pages/Accounts/TwoFactorPage.xaml.cs | 16 ++-- .../Pages/Accounts/TwoFactorPageViewModel.cs | 24 ++--- .../Accounts/UpdateTempPasswordPage.xaml.cs | 16 ++-- .../UpdateTempPasswordPageViewModel.cs | 18 ++-- .../Accounts/VerificationCodeViewModel.cs | 4 +- src/App/Pages/BaseContentPage.cs | 4 +- src/App/Pages/CaptchaProtectedViewModel.cs | 2 +- .../Generator/GeneratorHistoryPage.xaml.cs | 3 +- src/App/Pages/Generator/GeneratorPage.xaml.cs | 4 +- .../Pages/Generator/GeneratorPageViewModel.cs | 8 +- src/App/Pages/Send/SendAddEditPage.xaml.cs | 6 +- .../Pages/Send/SendAddEditPageViewModel.cs | 10 +- .../SendGroupingsPage.xaml.cs | 6 +- .../SendGroupingsPageHeaderListItem.cs | 2 +- .../SendGroupingsPageViewModel.cs | 2 +- src/App/Pages/Send/SendsPageViewModel.cs | 8 +- .../Settings/AutofillServicesPage.xaml.cs | 6 +- .../Settings/AutofillServicesPageViewModel.cs | 32 +++---- .../Settings/ExportVaultPageViewModel.cs | 6 +- .../Pages/Settings/ExtensionPageViewModel.cs | 4 +- .../Pages/Settings/FolderAddEditPage.xaml.cs | 4 +- .../Settings/FolderAddEditPageViewModel.cs | 4 +- src/App/Pages/Settings/FoldersPage.xaml.cs | 4 +- .../Pages/Settings/FoldersPageViewModel.cs | 8 +- .../Pages/Settings/OptionsPageViewModel.cs | 6 +- .../SettingsPage/SettingsPage.xaml.cs | 2 +- .../SettingsPage/SettingsPageViewModel.cs | 14 +-- src/App/Pages/Settings/SyncPageViewModel.cs | 4 +- src/App/Pages/TabsPage.cs | 4 +- src/App/Pages/Vault/AddEditPage.xaml.cs | 6 +- src/App/Pages/Vault/AddEditPageViewModel.cs | 12 +-- src/App/Pages/Vault/AttachmentsPage.xaml.cs | 4 +- .../Pages/Vault/AttachmentsPageViewModel.cs | 8 +- .../Pages/Vault/AutofillCiphersPage.xaml.cs | 14 +-- .../Vault/AutofillCiphersPageViewModel.cs | 12 +-- src/App/Pages/Vault/CiphersPage.xaml.cs | 8 +- src/App/Pages/Vault/CiphersPageViewModel.cs | 14 +-- .../Pages/Vault/CollectionsPageViewModel.cs | 8 +- .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 4 +- .../Pages/Vault/PasswordHistoryPage.xaml.cs | 3 +- .../Vault/PasswordHistoryPageViewModel.cs | 6 +- src/App/Pages/Vault/SharePageViewModel.cs | 8 +- src/App/Pages/Vault/ViewPage.xaml.cs | 6 +- src/App/Pages/Vault/ViewPageViewModel.cs | 10 +- src/App/Services/MobileI18nService.cs | 6 +- .../Services/MobilePasswordRepromptService.cs | 4 +- .../Services/MobilePlatformUtilsService.cs | 2 +- src/App/Services/MobileStorageService.cs | 6 +- .../NoopPushNotificationListenerService.cs | 4 +- src/App/Services/PreferencesStorageService.cs | 8 +- .../PushNotificationListenerService.cs | 20 ++-- src/App/Services/SecureStorageService.cs | 4 +- src/App/Styles/Android.xaml.cs | 2 +- src/App/Styles/Black.xaml.cs | 2 +- src/App/Styles/Dark.xaml.cs | 2 +- src/App/Styles/Light.xaml.cs | 2 +- src/App/Styles/Nord.xaml.cs | 2 +- src/App/Styles/Variables.xaml.cs | 2 +- src/App/Styles/iOS.xaml.cs | 2 +- src/App/Utilities/AppHelpers.cs | 20 ++-- src/App/Utilities/I18nExtension.cs | 4 +- src/App/Utilities/PasswordFormatter.cs | 8 +- src/Core/Abstractions/IApiService.cs | 8 +- src/Core/Abstractions/IAuditService.cs | 2 +- src/Core/Abstractions/IBroadcasterService.cs | 6 +- src/Core/Abstractions/ICipherService.cs | 2 +- .../Abstractions/ICryptoFunctionService.cs | 4 +- src/Core/Abstractions/IEnvironmentService.cs | 2 +- src/Core/Abstractions/IExportService.cs | 2 +- src/Core/Abstractions/IFileUploadService.cs | 8 +- src/Core/Abstractions/IMessagingService.cs | 2 +- src/Core/Abstractions/INativeLogService.cs | 2 +- src/Core/Abstractions/IPolicyService.cs | 2 +- src/Core/Abstractions/ISendService.cs | 2 +- src/Core/Abstractions/ITotpService.cs | 2 +- src/Core/BitwardenIcons.cs | 2 +- src/Core/Constants.cs | 6 +- src/Core/Enums/ClientType.cs | 2 +- src/Core/Enums/HdkfAlgorithm.cs | 2 +- src/Core/Enums/LinkedIdType.cs | 7 +- src/Core/Enums/SendType.cs | 2 +- src/Core/Exceptions/ApiException.cs | 4 +- src/Core/Models/Api/SendFileApi.cs | 2 +- src/Core/Models/Api/SendTextApi.cs | 2 +- src/Core/Models/Data/CipherData.cs | 4 +- src/Core/Models/Data/EventData.cs | 4 +- src/Core/Models/Data/FolderData.cs | 4 +- src/Core/Models/Data/LoginData.cs | 4 +- src/Core/Models/Data/PasswordHistoryData.cs | 4 +- src/Core/Models/Data/Permissions.cs | 2 +- src/Core/Models/Data/SendData.cs | 4 +- src/Core/Models/Data/SendFileData.cs | 2 +- src/Core/Models/Data/SendTextData.cs | 2 +- src/Core/Models/Domain/Attachment.cs | 8 +- src/Core/Models/Domain/AuthResult.cs | 4 +- src/Core/Models/Domain/Card.cs | 6 +- src/Core/Models/Domain/Cipher.cs | 8 +- src/Core/Models/Domain/Collection.cs | 6 +- src/Core/Models/Domain/EncByteArray.cs | 2 +- src/Core/Models/Domain/EncString.cs | 6 +- src/Core/Models/Domain/Field.cs | 6 +- src/Core/Models/Domain/Folder.cs | 6 +- src/Core/Models/Domain/Identity.cs | 6 +- src/Core/Models/Domain/Login.cs | 6 +- src/Core/Models/Domain/LoginUri.cs | 6 +- src/Core/Models/Domain/PasswordHistory.cs | 6 +- src/Core/Models/Domain/Policy.cs | 2 +- .../Domain/ResetPasswordPolicyOptions.cs | 2 +- src/Core/Models/Domain/SecureNote.cs | 4 +- src/Core/Models/Domain/Send.cs | 2 +- src/Core/Models/Domain/SendFile.cs | 2 +- src/Core/Models/Domain/SendText.cs | 2 +- src/Core/Models/Domain/SymmetricCryptoKey.cs | 4 +- src/Core/Models/Export/CollectionWithId.cs | 2 +- .../Models/Request/CipherCreateRequest.cs | 4 +- src/Core/Models/Request/CipherRequest.cs | 8 +- src/Core/Models/Request/CipherShareRequest.cs | 4 +- src/Core/Models/Request/EventRequest.cs | 4 +- ...ationUserResetPasswordEnrollmentRequest.cs | 2 +- src/Core/Models/Request/SendRequest.cs | 4 +- src/Core/Models/Request/TokenRequest.cs | 6 +- .../Response/AttachmentUploadDataReponse.cs | 2 +- src/Core/Models/Response/CipherResponse.cs | 6 +- src/Core/Models/Response/ErrorResponse.cs | 4 +- .../Response/IdentityCaptchaResponse.cs | 4 +- src/Core/Models/Response/IdentityResponse.cs | 2 +- .../Response/IdentityTwoFactorResponse.cs | 4 +- .../Models/Response/NotificationResponse.cs | 4 +- .../OrganizationAutoEnrollStatusResponse.cs | 2 +- .../Response/OrganizationKeysResponse.cs | 2 +- src/Core/Models/Response/SendResponse.cs | 2 +- src/Core/Models/View/CardView.cs | 6 +- src/Core/Models/View/CipherView.cs | 6 +- src/Core/Models/View/FolderView.cs | 4 +- src/Core/Models/View/IdentityView.cs | 4 +- src/Core/Models/View/ItemView.cs | 2 +- src/Core/Models/View/LoginUriView.cs | 8 +- src/Core/Models/View/LoginView.cs | 6 +- src/Core/Models/View/PasswordHistoryView.cs | 6 +- src/Core/Models/View/SecureNoteView.cs | 4 +- src/Core/Models/View/SendFileView.cs | 2 +- src/Core/Models/View/SendTextView.cs | 2 +- src/Core/Models/View/SendView.cs | 2 +- src/Core/Services/ApiService.cs | 22 ++--- src/Core/Services/AppIdService.cs | 4 +- src/Core/Services/AuditService.cs | 8 +- src/Core/Services/AuthService.cs | 8 +- src/Core/Services/AzureFileUploadService.cs | 92 +++++++++---------- .../Services/BitwardenFileUploadService.cs | 2 +- src/Core/Services/BroadcasterService.cs | 6 +- src/Core/Services/CipherService.cs | 18 ++-- src/Core/Services/CollectionService.cs | 12 +-- src/Core/Services/ConsoleLogService.cs | 4 +- src/Core/Services/CryptoService.cs | 14 +-- src/Core/Services/EventService.cs | 10 +- src/Core/Services/FileUploadService.cs | 10 +- src/Core/Services/FolderService.cs | 12 +-- src/Core/Services/InMemoryStorageService.cs | 6 +- src/Core/Services/KeyConnectorService.cs | 2 +- src/Core/Services/LiteDbStorageService.cs | 6 +- src/Core/Services/OrganizationService.cs | 8 +- .../Services/PasswordGenerationService.cs | 22 ++--- src/Core/Services/PclCryptoFunctionService.cs | 8 +- src/Core/Services/PolicyService.cs | 2 +- src/Core/Services/SearchService.cs | 6 +- src/Core/Services/SendService.cs | 11 ++- src/Core/Services/SettingsService.cs | 6 +- src/Core/Services/StateMigrationService.cs | 2 +- src/Core/Services/StateService.cs | 8 +- src/Core/Services/SyncService.cs | 12 +-- src/Core/Services/TokenService.cs | 10 +- src/Core/Services/TotpService.cs | 6 +- src/Core/Services/VaultTimeoutService.cs | 23 +++-- src/Core/Utilities/CoreHelpers.cs | 8 +- src/Core/Utilities/ExtendedViewModel.cs | 2 +- src/Core/Utilities/ServiceContainer.cs | 24 ++--- store/google/Publisher/Program.cs | 10 +- .../Attributes/AutoSubDataAttribute.cs | 2 +- .../Attributes/CustomAutoDataAttribute.cs | 2 +- .../Attributes/InlineAutoSubDataAttribute.cs | 2 +- .../InlineCustomAutoDataAttribute.cs | 6 +- .../Attributes/InlineSutAutoDataAttribute.cs | 2 +- .../Attributes/SutAutoDataAttribute.cs | 2 +- test/Common/AutoFixture/FixtureExtensions.cs | 2 +- test/Common/AutoFixture/ISutProvider.cs | 2 +- test/Common/AutoFixture/SutProvider.cs | 6 +- .../AutoFixture/SutProviderCustomization.cs | 2 +- test/Common/TestHelper.cs | 6 +- .../Cipher/CipherCustomizations.cs | 2 +- .../Domain/SymmetricCryptoKeyCustomization.cs | 2 +- .../AutoFixture/Send/SendCustomizations.cs | 4 +- test/Core.Test/Models/Data/SendDataTests.cs | 2 +- test/Core.Test/Models/Domain/SendTests.cs | 12 +-- .../Models/Request/SendRequestTests.cs | 2 +- test/Core.Test/Services/CipherServiceTests.cs | 6 +- .../Services/CryptoFunctionServiceTests.cs | 12 +-- test/Core.Test/Services/SendServiceTests.cs | 22 ++--- test/Playground/Program.cs | 4 +- 222 files changed, 723 insertions(+), 706 deletions(-) diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs index 350f791db..28eb7b712 100644 --- a/src/App/Abstractions/IDeviceActionService.cs +++ b/src/App/Abstractions/IDeviceActionService.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; +using System.Threading.Tasks; +using Bit.Core.Enums; using Bit.Core.Models.View; -using System.Threading.Tasks; namespace Bit.App.Abstractions { diff --git a/src/App/Abstractions/IPasswordRepromptService.cs b/src/App/Abstractions/IPasswordRepromptService.cs index 6acd0ddd2..579d9ab44 100644 --- a/src/App/Abstractions/IPasswordRepromptService.cs +++ b/src/App/Abstractions/IPasswordRepromptService.cs @@ -7,7 +7,7 @@ namespace Bit.App.Abstractions string[] ProtectedFields { get; } Task ShowPasswordPromptAsync(); - + Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync(); Task Enabled(); diff --git a/src/App/Abstractions/IPushNotificationListenerService.cs b/src/App/Abstractions/IPushNotificationListenerService.cs index 65104a1c9..4a57c75a5 100644 --- a/src/App/Abstractions/IPushNotificationListenerService.cs +++ b/src/App/Abstractions/IPushNotificationListenerService.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; namespace Bit.App.Abstractions { diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 8391cbf4b..bf6736836 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -1,15 +1,15 @@ -using Bit.App.Abstractions; +using System; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Models; using Bit.App.Pages; using Bit.App.Resources; using Bit.App.Services; using Bit.App.Utilities; using Bit.Core.Abstractions; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; -using Bit.Core.Enums; using Xamarin.Forms; using Xamarin.Forms.Xaml; @@ -318,7 +318,7 @@ namespace Bit.App Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null; Current.MainPage = new NavigationPage(new LoginPage(email, Options)); } - else if (await _vaultTimeoutService.IsLockedAsync() || + else if (await _vaultTimeoutService.IsLockedAsync() || await _vaultTimeoutService.ShouldLockAsync()) { Current.MainPage = new NavigationPage(new LockPage(Options)); diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs index 33b07ec8e..8096f9e19 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs @@ -99,7 +99,7 @@ namespace Bit.App.Controls Opacity = 0; IsVisible = true; this.FadeTo(1, 100); - + if (Device.RuntimePlatform == Device.Android && MainFab != null) { // start fab fade-out diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs index 496496035..3f789b4d1 100644 --- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs +++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs @@ -22,11 +22,11 @@ namespace Bit.App.Controls { _stateService = stateService; _messagingService = messagingService; - + SelectAccountCommand = new AsyncCommand(SelectAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); - + LongPressAccountCommand = new AsyncCommand>(LongPressAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs b/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs index 3bf27bcb6..fa38816a5 100644 --- a/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs +++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml.cs @@ -1,5 +1,5 @@ +using System.Windows.Input; using Bit.Core.Models.View; -using System.Windows.Input; using Xamarin.Forms; namespace Bit.App.Controls diff --git a/src/App/Controls/AvatarImageSource.cs b/src/App/Controls/AvatarImageSource.cs index 19138c853..fd9a86308 100644 --- a/src/App/Controls/AvatarImageSource.cs +++ b/src/App/Controls/AvatarImageSource.cs @@ -112,13 +112,13 @@ namespace Bit.App.Controls private string GetFirstLetters(string data, int charCount) { - var parts = data.Split(); + var parts = data.Split(); if (parts.Length > 1 && charCount <= 2) { var text = ""; for (int i = 0; i < charCount; i++) { - text += parts[i].Substring(0,1); + text += parts[i].Substring(0, 1); } return text; } diff --git a/src/App/Controls/ExtendedStepper.cs b/src/App/Controls/ExtendedStepper.cs index 68191ae20..88103eed5 100644 --- a/src/App/Controls/ExtendedStepper.cs +++ b/src/App/Controls/ExtendedStepper.cs @@ -6,7 +6,7 @@ namespace Bit.App.Controls { public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create( nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default); - + public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create( nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default); @@ -15,7 +15,7 @@ namespace Bit.App.Controls get => (Color)GetValue(StepperBackgroundColorProperty); set => SetValue(StepperBackgroundColorProperty, value); } - + public Color StepperForegroundColor { get => (Color)GetValue(StepperForegroundColorProperty); diff --git a/src/App/Controls/SendViewCell/SendViewCell.xaml.cs b/src/App/Controls/SendViewCell/SendViewCell.xaml.cs index 0b01c1004..094ac4521 100644 --- a/src/App/Controls/SendViewCell/SendViewCell.xaml.cs +++ b/src/App/Controls/SendViewCell/SendViewCell.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using Bit.App.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; @@ -13,7 +13,7 @@ namespace Bit.App.Controls public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create( nameof(ButtonCommand), typeof(Command), typeof(SendViewCell)); - + public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create( nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay); diff --git a/src/App/Effects/FabShadowEffect.cs b/src/App/Effects/FabShadowEffect.cs index 3e75d85d9..1ee706891 100644 --- a/src/App/Effects/FabShadowEffect.cs +++ b/src/App/Effects/FabShadowEffect.cs @@ -4,8 +4,8 @@ namespace Bit.App.Effects { public class FabShadowEffect : RoutingEffect { - public FabShadowEffect() - : base("Bitwarden.FabShadowEffect") + public FabShadowEffect() + : base("Bitwarden.FabShadowEffect") { } } } diff --git a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs index 9a6896428..96443341f 100644 --- a/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs +++ b/src/App/Pages/Accounts/BaseChangePasswordViewModel.cs @@ -1,12 +1,12 @@ -using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Bit.App.Abstractions; +using Bit.App.Resources; +using Bit.Core.Abstractions; using Bit.Core.Models.Domain; +using Bit.Core.Utilities; using Xamarin.Essentials; namespace Bit.App.Pages @@ -66,7 +66,7 @@ namespace Bit.App.Pages get => _policy; set => SetProperty(ref _policy, value); } - + public string ShowPasswordIcon => ShowPassword ? "" : ""; public string MasterPassword { get; set; } public string ConfirmMasterPassword { get; set; } @@ -84,7 +84,7 @@ namespace Bit.App.Pages await CheckPasswordPolicy(); } } - + private async Task CheckPasswordPolicy() { Policy = await _policyService.GetMasterPasswordPolicyOptions(); @@ -166,7 +166,7 @@ namespace Bit.App.Pages AppResources.AnErrorHasOccurred, AppResources.Ok); return false; } - + return true; } diff --git a/src/App/Pages/Accounts/EnvironmentPage.xaml.cs b/src/App/Pages/Accounts/EnvironmentPage.xaml.cs index 1f631af71..b57d2f440 100644 --- a/src/App/Pages/Accounts/EnvironmentPage.xaml.cs +++ b/src/App/Pages/Accounts/EnvironmentPage.xaml.cs @@ -1,8 +1,8 @@ -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using System; +using System; using System.Threading.Tasks; using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Accounts/EnvironmentPageViewModel.cs b/src/App/Pages/Accounts/EnvironmentPageViewModel.cs index c47491668..b25113c41 100644 --- a/src/App/Pages/Accounts/EnvironmentPageViewModel.cs +++ b/src/App/Pages/Accounts/EnvironmentPageViewModel.cs @@ -1,8 +1,8 @@ -using Bit.App.Resources; +using System; +using System.Threading.Tasks; +using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Accounts/HintPageViewModel.cs b/src/App/Pages/Accounts/HintPageViewModel.cs index cb316759f..b074012e8 100644 --- a/src/App/Pages/Accounts/HintPageViewModel.cs +++ b/src/App/Pages/Accounts/HintPageViewModel.cs @@ -1,9 +1,9 @@ -using Bit.App.Abstractions; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Accounts/HomePage.xaml.cs b/src/App/Pages/Accounts/HomePage.xaml.cs index 9963523c2..d63413216 100644 --- a/src/App/Pages/Accounts/HomePage.xaml.cs +++ b/src/App/Pages/Accounts/HomePage.xaml.cs @@ -87,7 +87,7 @@ namespace Bit.App.Pages { _logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png"; } - + private void Cancel_Clicked(object sender, EventArgs e) { if (DoOnce()) @@ -117,7 +117,7 @@ namespace Bit.App.Pages _vm.StartRegisterAction(); } } - + private async Task StartRegisterAsync() { var page = new RegisterPage(this); @@ -145,7 +145,7 @@ namespace Bit.App.Pages _vm.StartEnvironmentAction(); } } - + private async Task StartEnvironmentAsync() { await _accountListOverlay.HideAsync(); diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index a4515c01b..727a59cf9 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -272,7 +272,7 @@ namespace Bit.App.Pages var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations); var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var passwordValid = false; - + if (storedKeyHash != null) { passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key); diff --git a/src/App/Pages/Accounts/LoginPage.xaml.cs b/src/App/Pages/Accounts/LoginPage.xaml.cs index e6acef02e..22e3f49e2 100644 --- a/src/App/Pages/Accounts/LoginPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginPage.xaml.cs @@ -177,7 +177,7 @@ namespace Bit.App.Pages var previousPage = await AppHelpers.ClearPreviousPage(); Application.Current.MainPage = new TabsPage(_appOptions, previousPage); } - + private async Task UpdateTempPasswordAsync() { var page = new UpdateTempPasswordPage(); diff --git a/src/App/Pages/Accounts/LoginPageViewModel.cs b/src/App/Pages/Accounts/LoginPageViewModel.cs index 709d7cc80..a895e39a6 100644 --- a/src/App/Pages/Accounts/LoginPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginPageViewModel.cs @@ -67,7 +67,7 @@ namespace Bit.App.Pages get => _showCancelButton; set => SetProperty(ref _showCancelButton, value); } - + public string Email { get => _email; diff --git a/src/App/Pages/Accounts/LoginSsoPage.xaml.cs b/src/App/Pages/Accounts/LoginSsoPage.xaml.cs index f89dfbaa7..5b7878e55 100644 --- a/src/App/Pages/Accounts/LoginSsoPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginSsoPage.xaml.cs @@ -1,9 +1,9 @@ -using Bit.App.Models; +using System; +using System.Threading.Tasks; +using Bit.App.Models; +using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; -using Bit.App.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -99,7 +99,7 @@ namespace Bit.App.Pages var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier); await Navigation.PushModalAsync(new NavigationPage(page)); } - + private async Task UpdateTempPasswordAsync() { var page = new UpdateTempPasswordPage(); diff --git a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs index 4c7d34595..66145457a 100644 --- a/src/App/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginSsoPageViewModel.cs @@ -1,13 +1,13 @@ -using Bit.App.Abstractions; -using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using System; +using System; using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Resources; using Bit.App.Utilities; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; +using Bit.Core.Utilities; using Xamarin.Essentials; using Xamarin.Forms; @@ -170,7 +170,7 @@ namespace Bit.App.Pages else if (response.ResetMasterPassword) { StartSetPasswordAction?.Invoke(); - } + } else if (response.ForcePasswordReset) { UpdateTempPasswordAction?.Invoke(); diff --git a/src/App/Pages/Accounts/RegisterPage.xaml.cs b/src/App/Pages/Accounts/RegisterPage.xaml.cs index 481ef8c74..7a98c9bab 100644 --- a/src/App/Pages/Accounts/RegisterPage.xaml.cs +++ b/src/App/Pages/Accounts/RegisterPage.xaml.cs @@ -55,7 +55,7 @@ namespace Bit.App.Pages await _vm.SubmitAsync(); } } - + private async Task RegistrationSuccessAsync(HomePage homePage) { if (homePage != null) diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs index c27c8a79a..a8ddb16c4 100644 --- a/src/App/Pages/Accounts/RegisterPageViewModel.cs +++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs @@ -1,14 +1,14 @@ -using Bit.App.Abstractions; +using System; +using System.Threading.Tasks; +using System.Windows.Input; +using Bit.App.Abstractions; using Bit.App.Resources; +using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Request; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; -using System.Windows.Input; -using Bit.Core; using Xamarin.Forms; namespace Bit.App.Pages @@ -39,7 +39,7 @@ namespace Bit.App.Pages SubmitCommand = new Command(async () => await SubmitAsync()); ShowTerms = !_platformUtilsService.IsSelfHost(); } - + public ICommand PoliciesClickCommand => new Command((url) => { _platformUtilsService.LaunchUri(url); @@ -54,20 +54,20 @@ namespace Bit.App.Pages nameof(ShowPasswordIcon) }); } - + public bool AcceptPolicies { get => _acceptPolicies; set => SetProperty(ref _acceptPolicies, value); } - + public Thickness SwitchMargin { - get => Device.RuntimePlatform == Device.Android - ? new Thickness(0, 0, 0, 0) + get => Device.RuntimePlatform == Device.Android + ? new Thickness(0, 0, 0, 0) : new Thickness(0, 0, 10, 0); } - + public bool ShowTerms { get; set; } public Command SubmitCommand { get; } public Command TogglePasswordCommand { get; } @@ -136,7 +136,7 @@ namespace Bit.App.Pages } // TODO: Password strength check? - + if (showLoading) { await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs index ca913d905..7be7177d3 100644 --- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs @@ -1,17 +1,17 @@ -using Bit.App.Abstractions; -using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.Models.Request; -using Bit.Core.Utilities; -using System; +using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Resources; using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Domain; +using Bit.Core.Models.Request; +using Bit.Core.Utilities; using Xamarin.Essentials; using Xamarin.Forms; @@ -63,7 +63,7 @@ namespace Bit.App.Pages get => _isPolicyInEffect; set => SetProperty(ref _isPolicyInEffect, value); } - + public bool ResetPasswordAutoEnroll { get => _resetPasswordAutoEnroll; @@ -220,7 +220,7 @@ namespace Bit.App.Pages // Enroll user await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest); } - + await _deviceActionService.HideLoadingAsync(); SetPasswordSuccessAction?.Invoke(); } diff --git a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs index e539f8026..208724e71 100644 --- a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs +++ b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs @@ -1,11 +1,11 @@ -using Bit.App.Controls; +using System; +using System.Threading.Tasks; +using Bit.App.Controls; using Bit.App.Models; using Bit.App.Resources; +using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; -using Bit.App.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -47,7 +47,8 @@ namespace Bit.App.Pages if (Device.RuntimePlatform == Device.iOS) { ToolbarItems.Add(_moreItem); - } else + } + else { ToolbarItems.Add(_useAnotherTwoStepMethod); } @@ -92,7 +93,8 @@ namespace Bit.App.Pages if (_vm.TotpMethod) { RequestFocus(_totpEntry); - } else if (_vm.YubikeyMethod) + } + else if (_vm.YubikeyMethod) { RequestFocus(_yubikeyTokenEntry); } @@ -187,7 +189,7 @@ namespace Bit.App.Pages var page = new SetPasswordPage(_appOptions, _orgIdentifier); await Navigation.PushModalAsync(new NavigationPage(page)); } - + private async Task UpdateTempPasswordAsync() { var page = new UpdateTempPasswordPage(); diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs index edba7a2de..15de50f51 100644 --- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs @@ -1,16 +1,16 @@ -using Bit.App.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; +using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Request; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Bit.App.Utilities; using Newtonsoft.Json; using Xamarin.Essentials; using Xamarin.Forms; @@ -113,7 +113,7 @@ namespace Bit.App.Pages public Action StartSetPasswordAction { get; set; } public Action CloseAction { get; set; } public Action UpdateTempPasswordAction { get; set; } - + protected override II18nService i18nService => _i18nService; protected override IEnvironmentService environmentService => _environmentService; protected override IDeviceActionService deviceActionService => _deviceActionService; @@ -293,7 +293,7 @@ namespace Bit.App.Pages await _deviceActionService.ShowLoadingAsync(AppResources.Validating); } var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, _captchaToken, Remember); - + if (result.CaptchaNeeded) { if (await HandleCaptchaAsync(result.CaptchaSiteKey)) @@ -304,12 +304,12 @@ namespace Bit.App.Pages return; } _captchaToken = null; - + var task = Task.Run(() => _syncService.FullSyncAsync(true)); await _deviceActionService.HideLoadingAsync(); _messagingService.Send("listenYubiKeyOTP", false); _broadcasterService.Unsubscribe(nameof(TwoFactorPage)); - + if (_authingWithSso && result.ResetMasterPassword) { StartSetPasswordAction?.Invoke(); @@ -317,7 +317,7 @@ namespace Bit.App.Pages else if (result.ForcePasswordReset) { UpdateTempPasswordAction?.Invoke(); - } + } else { TwoFactorAuthSuccessAction?.Invoke(); diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs index 2e016606f..d814a854d 100644 --- a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs +++ b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml.cs @@ -1,7 +1,7 @@ +using System; +using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System; -using Bit.App.Resources; using Xamarin.Forms; namespace Bit.App.Pages @@ -14,29 +14,29 @@ namespace Bit.App.Pages private readonly string _pageName; public UpdateTempPasswordPage() - { + { // Service Init _messagingService = ServiceContainer.Resolve("messagingService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - + // Binding InitializeComponent(); _pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks); _vm = BindingContext as UpdateTempPasswordPageViewModel; _vm.Page = this; SetActivityIndicator(); - + // Actions Declaration _vm.LogOutAction = () => { _messagingService.Send("logout"); }; _vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess); - + // Link fields that will be referenced in codebehind MasterPasswordEntry = _masterPassword; ConfirmMasterPasswordEntry = _confirmMasterPassword; - + // Return Types and Commands _masterPassword.ReturnType = ReturnType.Next; _masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus()); @@ -77,7 +77,7 @@ namespace Bit.App.Pages } } } - + private void UpdateTempPasswordSuccess() { _messagingService.Send("logout"); diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs index 42ea72f6a..918e40fdc 100644 --- a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs @@ -1,6 +1,6 @@ -using Bit.App.Resources; -using System; +using System; using System.Threading.Tasks; +using Bit.App.Resources; using Bit.Core.Exceptions; using Bit.Core.Models.Request; using Xamarin.Forms; @@ -16,7 +16,7 @@ namespace Bit.App.Pages ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); SubmitCommand = new Command(async () => await SubmitAsync()); } - + public Command SubmitCommand { get; } public Command TogglePasswordCommand { get; } public Command ToggleConfirmPasswordCommand { get; } @@ -37,23 +37,23 @@ namespace Bit.App.Pages public async Task SubmitAsync() { - if (!await ValidateMasterPasswordAsync()) + if (!await ValidateMasterPasswordAsync()) { return; } - + // Retrieve details for key generation var kdf = await _stateService.GetKdfTypeAsync(); var kdfIterations = await _stateService.GetKdfIterationsAsync(); var email = await _stateService.GetEmailAsync(); - + // Create new key and hash new password var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); - + // Create new encKey for the User var newEncKey = await _cryptoService.RemakeEncKeyAsync(key); - + // Create request var request = new UpdateTempPasswordRequest { @@ -61,7 +61,7 @@ namespace Bit.App.Pages NewMasterPasswordHash = masterPasswordHash, MasterPasswordHint = Hint }; - + // Initiate API action try { diff --git a/src/App/Pages/Accounts/VerificationCodeViewModel.cs b/src/App/Pages/Accounts/VerificationCodeViewModel.cs index 7f1c62bbb..7e1257cd7 100644 --- a/src/App/Pages/Accounts/VerificationCodeViewModel.cs +++ b/src/App/Pages/Accounts/VerificationCodeViewModel.cs @@ -36,7 +36,7 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve("logger"); PageTitle = AppResources.VerificationCode; - + TogglePasswordCommand = new Command(TogglePassword); MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false); RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false); @@ -60,7 +60,7 @@ namespace Bit.App.Pages get => _mainActionText; set => SetProperty(ref _mainActionText, value); } - + public string SendCodeStatus { get => _sendCodeStatus; diff --git a/src/App/Pages/BaseContentPage.cs b/src/App/Pages/BaseContentPage.cs index 7d7fafb67..c6e5b6faf 100644 --- a/src/App/Pages/BaseContentPage.cs +++ b/src/App/Pages/BaseContentPage.cs @@ -35,12 +35,12 @@ namespace Bit.App.Pages protected async override void OnAppearing() { base.OnAppearing(); - + if (IsThemeDirty) { UpdateOnThemeChanged(); } - + await SaveActivityAsync(); } diff --git a/src/App/Pages/CaptchaProtectedViewModel.cs b/src/App/Pages/CaptchaProtectedViewModel.cs index d5d019dff..d31d338ae 100644 --- a/src/App/Pages/CaptchaProtectedViewModel.cs +++ b/src/App/Pages/CaptchaProtectedViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Resources; diff --git a/src/App/Pages/Generator/GeneratorHistoryPage.xaml.cs b/src/App/Pages/Generator/GeneratorHistoryPage.xaml.cs index c4f75644c..e33b04dcb 100644 --- a/src/App/Pages/Generator/GeneratorHistoryPage.xaml.cs +++ b/src/App/Pages/Generator/GeneratorHistoryPage.xaml.cs @@ -31,7 +31,8 @@ namespace Bit.App.Pages { base.OnAppearing(); - await LoadOnAppearedAsync(_mainLayout, true, async () => { + await LoadOnAppearedAsync(_mainLayout, true, async () => + { await _vm.InitAsync(); }); } diff --git a/src/App/Pages/Generator/GeneratorPage.xaml.cs b/src/App/Pages/Generator/GeneratorPage.xaml.cs index d642e43b0..8d0e942af 100644 --- a/src/App/Pages/Generator/GeneratorPage.xaml.cs +++ b/src/App/Pages/Generator/GeneratorPage.xaml.cs @@ -12,7 +12,7 @@ namespace Bit.App.Pages public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage { private readonly IBroadcasterService _broadcasterService; - + private GeneratorPageViewModel _vm; private readonly bool _fromTabPage; private readonly Action _selectAction; @@ -77,7 +77,7 @@ namespace Bit.App.Pages } }); } - + protected override void OnDisappearing() { base.OnDisappearing(); diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs index dc54f67b7..1b694a83c 100644 --- a/src/App/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs @@ -1,10 +1,10 @@ -using Bit.App.Resources; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.App.Resources; using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Models.Domain; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages @@ -225,7 +225,7 @@ namespace Bit.App.Pages } } } - + public PasswordGeneratorPolicyOptions EnforcedPolicyOptions { get => _enforcedPolicyOptions; diff --git a/src/App/Pages/Send/SendAddEditPage.xaml.cs b/src/App/Pages/Send/SendAddEditPage.xaml.cs index 0f4ba6f4e..85a52022d 100644 --- a/src/App/Pages/Send/SendAddEditPage.xaml.cs +++ b/src/App/Pages/Send/SendAddEditPage.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Bit.App.Models; @@ -337,10 +337,10 @@ namespace Bit.App.Pages _vm.IsAddFromShare = true; _vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving; - + var name = _appOptions.CreateSend.Item2; _vm.Send.Name = name; - + var type = _appOptions.CreateSend.Item1; if (type == SendType.File) { diff --git a/src/App/Pages/Send/SendAddEditPageViewModel.cs b/src/App/Pages/Send/SendAddEditPageViewModel.cs index 78642e362..01d588170 100644 --- a/src/App/Pages/Send/SendAddEditPageViewModel.cs +++ b/src/App/Pages/Send/SendAddEditPageViewModel.cs @@ -40,7 +40,7 @@ namespace Bit.App.Pages private TimeSpan? _expirationTime; private bool _isOverridingPickers; private int? _maxAccessCount; - private string[] _additionalSendProperties = new [] + private string[] _additionalSendProperties = new[] { nameof(IsText), nameof(IsFile), @@ -209,7 +209,7 @@ namespace Bit.App.Pages { get => _showPassword; set => SetProperty(ref _showPassword, value, - additionalPropertyNames: new [] + additionalPropertyNames: new[] { nameof(ShowPasswordIcon) }); @@ -237,7 +237,7 @@ namespace Bit.App.Pages PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend; _canAccessPremium = await _stateService.CanAccessPremiumAsync(); _emailVerified = await _stateService.GetEmailVerifiedAsync(); - SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync(); + SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync(); SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail; } @@ -381,7 +381,7 @@ namespace Bit.App.Pages UpdateSendData(); - if (string.IsNullOrWhiteSpace(NewPassword)) + if (string.IsNullOrWhiteSpace(NewPassword)) { NewPassword = null; } @@ -521,7 +521,7 @@ namespace Bit.App.Pages { await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired); } - + if (IsAddFromShare && Device.RuntimePlatform == Device.Android) { _deviceActionService.CloseMainApp(); diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs index 7bdf38b7a..183a832d8 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; using Bit.App.Controls; @@ -133,7 +133,7 @@ namespace Bit.App.Pages } } } - + private async void RowSelected(object sender, SelectionChangedEventArgs e) { ((ExtendedCollectionView)sender).SelectedItem = null; @@ -174,7 +174,7 @@ namespace Bit.App.Pages { await _vaultTimeoutService.LockAsync(true, true); } - + private void About_Clicked(object sender, EventArgs e) { _vm.ShowAbout(); diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs index c8c7943be..43b35e669 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs @@ -9,6 +9,6 @@ } public string Title { get; } - public string ItemCount { get; } + public string ItemCount { get; } } } diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs index 4c7e70c37..ada402713 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs @@ -136,7 +136,7 @@ namespace Bit.App.Pages ShowNoData = false; Loading = true; ShowList = false; - SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync(); + SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); var groupedSends = new List(); var page = Page as SendGroupingsPage; diff --git a/src/App/Pages/Send/SendsPageViewModel.cs b/src/App/Pages/Send/SendsPageViewModel.cs index 94b807459..5afe8e5cd 100644 --- a/src/App/Pages/Send/SendsPageViewModel.cs +++ b/src/App/Pages/Send/SendsPageViewModel.cs @@ -35,11 +35,11 @@ namespace Bit.App.Pages get => _sendEnabled; set => SetProperty(ref _sendEnabled, value); } - + public bool ShowNoData { get => _showNoData; - set => SetProperty(ref _showNoData, value, additionalPropertyNames: new [] + set => SetProperty(ref _showNoData, value, additionalPropertyNames: new[] { nameof(ShowSearchDirection) }); @@ -48,7 +48,7 @@ namespace Bit.App.Pages public bool ShowList { get => _showList; - set => SetProperty(ref _showList, value, additionalPropertyNames: new [] + set => SetProperty(ref _showList, value, additionalPropertyNames: new[] { nameof(ShowSearchDirection) }); @@ -58,7 +58,7 @@ namespace Bit.App.Pages public async Task InitAsync() { - SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync(); + SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync(); if (!string.IsNullOrWhiteSpace((Page as SendsPage).SearchBar.Text)) { Search((Page as SendsPage).SearchBar.Text, 200); diff --git a/src/App/Pages/Settings/AutofillServicesPage.xaml.cs b/src/App/Pages/Settings/AutofillServicesPage.xaml.cs index 72719b784..906f2e617 100644 --- a/src/App/Pages/Settings/AutofillServicesPage.xaml.cs +++ b/src/App/Pages/Settings/AutofillServicesPage.xaml.cs @@ -49,12 +49,12 @@ namespace Bit.App.Pages _vm.ToggleAutofillService(); } } - + private void ToggleInlineAutofill(object sender, EventArgs e) { _vm.ToggleInlineAutofill(); } - + private void ToggleAccessibility(object sender, EventArgs e) { if (DoOnce()) @@ -62,7 +62,7 @@ namespace Bit.App.Pages _vm.ToggleAccessibility(); } } - + private void ToggleDrawOver(object sender, EventArgs e) { if (DoOnce()) diff --git a/src/App/Pages/Settings/AutofillServicesPageViewModel.cs b/src/App/Pages/Settings/AutofillServicesPageViewModel.cs index 52b3cf4c3..06720826f 100644 --- a/src/App/Pages/Settings/AutofillServicesPageViewModel.cs +++ b/src/App/Pages/Settings/AutofillServicesPageViewModel.cs @@ -12,7 +12,7 @@ namespace Bit.App.Pages private readonly IDeviceActionService _deviceActionService; private readonly IStateService _stateService; private readonly MobileI18nService _i18nService; - + private bool _autofillServiceToggled; private bool _inlineAutofillToggled; private bool _accessibilityToggled; @@ -26,9 +26,9 @@ namespace Bit.App.Pages _i18nService = ServiceContainer.Resolve("i18nService") as MobileI18nService; PageTitle = AppResources.AutofillServices; } - + #region Autofill Service - + public bool AutofillServiceVisible { get => _deviceActionService.SystemMajorVersion() >= 26; @@ -43,16 +43,16 @@ namespace Bit.App.Pages nameof(InlineAutofillEnabled) }); } - + #endregion - + #region Inline Autofill public bool InlineAutofillVisible { get => _deviceActionService.SystemMajorVersion() >= 30; } - + public bool InlineAutofillEnabled { get => AutofillServiceToggled; @@ -69,9 +69,9 @@ namespace Bit.App.Pages } } } - + #endregion - + #region Accessibility public string AccessibilityDescriptionLabel @@ -97,7 +97,7 @@ namespace Bit.App.Pages return _i18nService.T("AccessibilityDescription4"); } } - + public bool AccessibilityToggled { get => _accessibilityToggled; @@ -109,9 +109,9 @@ namespace Bit.App.Pages } #endregion - + #region Draw-Over - + public bool DrawOverVisible { get => _deviceActionService.SystemMajorVersion() >= 23; @@ -135,12 +135,12 @@ namespace Bit.App.Pages return _i18nService.T("DrawOverDescription3"); } } - + public bool DrawOverEnabled { get => AccessibilityToggled; } - + public bool DrawOverToggled { get => _drawOverToggled; @@ -148,7 +148,7 @@ namespace Bit.App.Pages } #endregion - + public async Task InitAsync() { InlineAutofillToggled = await _stateService.GetInlineAutofillEnabledAsync() ?? true; @@ -189,7 +189,7 @@ namespace Bit.App.Pages } _deviceActionService.OpenAccessibilityOverlayPermissionSettings(); } - + public void UpdateEnabled() { AutofillServiceToggled = @@ -197,7 +197,7 @@ namespace Bit.App.Pages AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning(); DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted(); } - + private async Task UpdateInlineAutofillToggledAsync() { if (_inited) diff --git a/src/App/Pages/Settings/ExportVaultPageViewModel.cs b/src/App/Pages/Settings/ExportVaultPageViewModel.cs index de84447ba..63a05b261 100644 --- a/src/App/Pages/Settings/ExportVaultPageViewModel.cs +++ b/src/App/Pages/Settings/ExportVaultPageViewModel.cs @@ -4,11 +4,11 @@ using System.Text; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Resources; +using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Bit.Core; using Xamarin.Forms; namespace Bit.App.Pages @@ -77,7 +77,7 @@ namespace Bit.App.Pages InstructionText = _i18nService.T("ExportVaultMasterPasswordDescription"); SecretName = _i18nService.T("MasterPassword"); } - + UpdateWarning(); } @@ -109,7 +109,7 @@ namespace Bit.App.Pages { get => _showPassword; set => SetProperty(ref _showPassword, value, - additionalPropertyNames: new string[] {nameof(ShowPasswordIcon)}); + additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) }); } public bool UseOTPVerification diff --git a/src/App/Pages/Settings/ExtensionPageViewModel.cs b/src/App/Pages/Settings/ExtensionPageViewModel.cs index 9427ca2f4..ea989be5d 100644 --- a/src/App/Pages/Settings/ExtensionPageViewModel.cs +++ b/src/App/Pages/Settings/ExtensionPageViewModel.cs @@ -1,7 +1,7 @@ -using Bit.App.Resources; +using System.Threading.Tasks; +using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System.Threading.Tasks; namespace Bit.App.Pages { diff --git a/src/App/Pages/Settings/FolderAddEditPage.xaml.cs b/src/App/Pages/Settings/FolderAddEditPage.xaml.cs index 958756e54..333dd1051 100644 --- a/src/App/Pages/Settings/FolderAddEditPage.xaml.cs +++ b/src/App/Pages/Settings/FolderAddEditPage.xaml.cs @@ -1,5 +1,5 @@ -using Bit.App.Resources; -using System.Collections.Generic; +using System.Collections.Generic; +using Bit.App.Resources; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Settings/FolderAddEditPageViewModel.cs b/src/App/Pages/Settings/FolderAddEditPageViewModel.cs index 33479ae68..08615a2f9 100644 --- a/src/App/Pages/Settings/FolderAddEditPageViewModel.cs +++ b/src/App/Pages/Settings/FolderAddEditPageViewModel.cs @@ -1,10 +1,10 @@ -using Bit.App.Abstractions; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Settings/FoldersPage.xaml.cs b/src/App/Pages/Settings/FoldersPage.xaml.cs index 723302cff..353f6f74a 100644 --- a/src/App/Pages/Settings/FoldersPage.xaml.cs +++ b/src/App/Pages/Settings/FoldersPage.xaml.cs @@ -1,7 +1,7 @@ -using Bit.Core.Models.View; -using System; +using System; using System.Linq; using Bit.App.Controls; +using Bit.Core.Models.View; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Settings/FoldersPageViewModel.cs b/src/App/Pages/Settings/FoldersPageViewModel.cs index 8bdb8004f..6e348f3af 100644 --- a/src/App/Pages/Settings/FoldersPageViewModel.cs +++ b/src/App/Pages/Settings/FoldersPageViewModel.cs @@ -1,10 +1,10 @@ -using Bit.App.Resources; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Bit.App.Pages { diff --git a/src/App/Pages/Settings/OptionsPageViewModel.cs b/src/App/Pages/Settings/OptionsPageViewModel.cs index 4ce6d39c8..fc6137190 100644 --- a/src/App/Pages/Settings/OptionsPageViewModel.cs +++ b/src/App/Pages/Settings/OptionsPageViewModel.cs @@ -1,11 +1,11 @@ -using Bit.App.Resources; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.App.Resources; using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs index 9d7fac2bf..8a798c11c 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs @@ -59,7 +59,7 @@ namespace Bit.App.Pages async void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args) { - var s = (TimePicker) sender; + var s = (TimePicker)sender; var time = s.Time.TotalMinutes; if (s.IsFocused && args.PropertyName == "Time") { diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 48b67bf1f..06727300f 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -1,16 +1,16 @@ -using Bit.App.Abstractions; -using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Resources; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; +using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; using ZXing.Client.Result; -using Xamarin.CommunityToolkit.ObjectModel; namespace Bit.App.Pages { @@ -428,7 +428,7 @@ namespace Bit.App.Pages var securityItems = new List { new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue }, - new SettingsPageListItem + new SettingsPageListItem { Name = AppResources.VaultTimeoutAction, SubLabel = _vaultTimeoutActionDisplayValue diff --git a/src/App/Pages/Settings/SyncPageViewModel.cs b/src/App/Pages/Settings/SyncPageViewModel.cs index 9cbfe52b6..7aade221b 100644 --- a/src/App/Pages/Settings/SyncPageViewModel.cs +++ b/src/App/Pages/Settings/SyncPageViewModel.cs @@ -1,9 +1,9 @@ -using Bit.App.Abstractions; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using System.Threading.Tasks; namespace Bit.App.Pages { diff --git a/src/App/Pages/TabsPage.cs b/src/App/Pages/TabsPage.cs index d24442eee..015d75a60 100644 --- a/src/App/Pages/TabsPage.cs +++ b/src/App/Pages/TabsPage.cs @@ -12,7 +12,7 @@ namespace Bit.App.Pages { private readonly IMessagingService _messagingService; private readonly IKeyConnectorService _keyConnectorService; - + private NavigationPage _groupingsPage; private NavigationPage _sendGroupingsPage; private NavigationPage _generatorPage; @@ -93,7 +93,7 @@ namespace Bit.App.Pages { CurrentPage = _generatorPage; } - + public void ResetToSendPage() { CurrentPage = _sendGroupingsPage; diff --git a/src/App/Pages/Vault/AddEditPage.xaml.cs b/src/App/Pages/Vault/AddEditPage.xaml.cs index 796866d8b..3329de8dd 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml.cs +++ b/src/App/Pages/Vault/AddEditPage.xaml.cs @@ -1,12 +1,12 @@ -using Bit.App.Abstractions; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Models; using Bit.App.Resources; using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Threading.Tasks; using Xamarin.Essentials; using Xamarin.Forms; using Xamarin.Forms.PlatformConfiguration; diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs index 40fd047f5..5c4e9a77e 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/AddEditPageViewModel.cs @@ -65,7 +65,7 @@ namespace Bit.App.Pages new KeyValuePair(UriMatchType.Exact, AppResources.Exact), new KeyValuePair(UriMatchType.Never, AppResources.Never) }; - + public AddEditPageViewModel() { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -350,7 +350,7 @@ namespace Bit.App.Pages { Cipher.Name += " - " + AppResources.Clone; // If not allowing personal ownership, update cipher's org Id to prompt downstream changes - if (Cipher.OrganizationId == null && !AllowPersonal) + if (Cipher.OrganizationId == null && !AllowPersonal) { Cipher.OrganizationId = OrganizationId; } @@ -399,7 +399,7 @@ namespace Bit.App.Pages IdentityTitleOptions.FindIndex(k => k.Value == Cipher.Identity.Title); OwnershipSelectedIndex = string.IsNullOrWhiteSpace(Cipher.OrganizationId) ? 0 : OwnershipOptions.FindIndex(k => k.Value == Cipher.OrganizationId); - + // If the selected organization is on Index 0 and we've removed the personal option, force refresh if (!AllowPersonal && OwnershipSelectedIndex == 0) { @@ -451,11 +451,11 @@ namespace Bit.App.Pages AppResources.Ok); return false; } - + if ((!EditMode || CloneMode) && !AllowPersonal && string.IsNullOrWhiteSpace(Cipher.OrganizationId)) { await Page.DisplayAlert(AppResources.AnErrorHasOccurred, - AppResources.PersonalOwnershipSubmitError,AppResources.Ok); + AppResources.PersonalOwnershipSubmitError, AppResources.Ok); return false; } @@ -535,7 +535,7 @@ namespace Bit.App.Pages AppResources.AnErrorHasOccurred); } } - catch(Exception genex) + catch (Exception genex) { _logger.Exception(genex); await _deviceActionService.HideLoadingAsync(); diff --git a/src/App/Pages/Vault/AttachmentsPage.xaml.cs b/src/App/Pages/Vault/AttachmentsPage.xaml.cs index 4af1b9148..6e9391866 100644 --- a/src/App/Pages/Vault/AttachmentsPage.xaml.cs +++ b/src/App/Pages/Vault/AttachmentsPage.xaml.cs @@ -1,6 +1,6 @@ -using Bit.Core.Abstractions; +using System; +using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Vault/AttachmentsPageViewModel.cs b/src/App/Pages/Vault/AttachmentsPageViewModel.cs index c854f984b..9dc284b42 100644 --- a/src/App/Pages/Vault/AttachmentsPageViewModel.cs +++ b/src/App/Pages/Vault/AttachmentsPageViewModel.cs @@ -1,13 +1,13 @@ -using Bit.App.Abstractions; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs b/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs index 3c680aa05..aed80316f 100644 --- a/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs +++ b/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs @@ -1,12 +1,12 @@ -using Bit.App.Models; -using Bit.Core.Abstractions; -using Bit.Core.Enums; -using Bit.Core.Utilities; -using System; +using System; using System.Linq; using System.Threading.Tasks; using Bit.App.Controls; +using Bit.App.Models; using Bit.App.Utilities; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Utilities; using Xamarin.Forms; namespace Bit.App.Pages @@ -72,14 +72,14 @@ namespace Bit.App.Pages }); } }); - + await LoadOnAppearedAsync(_mainLayout, false, async () => { try { await _vm.LoadAsync(); } - catch (Exception e) when(e.Message.Contains("No key.")) + catch (Exception e) when (e.Message.Contains("No key.")) { await Task.Delay(1000); await _vm.LoadAsync(); diff --git a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs index 77e0dea0a..80e9caf09 100644 --- a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs +++ b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs @@ -1,4 +1,8 @@ -using Bit.App.Abstractions; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Controls; using Bit.App.Models; using Bit.App.Resources; using Bit.App.Utilities; @@ -8,11 +12,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Xamarin.CommunityToolkit.ObjectModel; -using Bit.App.Controls; using Xamarin.Forms; namespace Bit.App.Pages @@ -44,7 +44,7 @@ namespace Bit.App.Pages GroupedItems = new ObservableRangeCollection(); CipherOptionsCommand = new Command(CipherOptionsAsync); - + AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { AllowAddAccountRow = false diff --git a/src/App/Pages/Vault/CiphersPage.xaml.cs b/src/App/Pages/Vault/CiphersPage.xaml.cs index c730eb5ea..87987ea43 100644 --- a/src/App/Pages/Vault/CiphersPage.xaml.cs +++ b/src/App/Pages/Vault/CiphersPage.xaml.cs @@ -1,10 +1,10 @@ -using Bit.App.Abstractions; +using System; +using System.Linq; +using Bit.App.Abstractions; +using Bit.App.Controls; using Bit.App.Resources; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System; -using System.Linq; -using Bit.App.Controls; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Vault/CiphersPageViewModel.cs b/src/App/Pages/Vault/CiphersPageViewModel.cs index 3406046dd..d9a5510e2 100644 --- a/src/App/Pages/Vault/CiphersPageViewModel.cs +++ b/src/App/Pages/Vault/CiphersPageViewModel.cs @@ -1,4 +1,9 @@ -using Bit.App.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core; using Bit.Core.Abstractions; @@ -6,11 +11,6 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages @@ -107,7 +107,7 @@ namespace Bit.App.Pages } try { - ciphers = await _searchService.SearchCiphersAsync(searchText, + ciphers = await _searchService.SearchCiphersAsync(searchText, Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token); cts.Token.ThrowIfCancellationRequested(); } diff --git a/src/App/Pages/Vault/CollectionsPageViewModel.cs b/src/App/Pages/Vault/CollectionsPageViewModel.cs index e0b678de2..f58ab1a78 100644 --- a/src/App/Pages/Vault/CollectionsPageViewModel.cs +++ b/src/App/Pages/Vault/CollectionsPageViewModel.cs @@ -1,13 +1,13 @@ -using Bit.App.Abstractions; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Bit.App.Pages { diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 395dd8cf2..2861b03fb 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -27,7 +27,7 @@ namespace Bit.App.Pages private PreviousPageInfo _previousPage; public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null, - string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null, + string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null, bool deleted = false) { _pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks); @@ -117,7 +117,7 @@ namespace Bit.App.Pages { await _vm.LoadAsync(); } - catch (Exception e) when(e.Message.Contains("No key.")) + catch (Exception e) when (e.Message.Contains("No key.")) { await Task.Delay(1000); await _vm.LoadAsync(); diff --git a/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs b/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs index ea444c3d6..27f271da3 100644 --- a/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs +++ b/src/App/Pages/Vault/PasswordHistoryPage.xaml.cs @@ -23,7 +23,8 @@ namespace Bit.App.Pages protected override async void OnAppearing() { base.OnAppearing(); - await LoadOnAppearedAsync(_mainLayout, true, async () => { + await LoadOnAppearedAsync(_mainLayout, true, async () => + { await _vm.InitAsync(); }); } diff --git a/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs b/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs index 2dc086322..e25550604 100644 --- a/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs +++ b/src/App/Pages/Vault/PasswordHistoryPageViewModel.cs @@ -1,9 +1,9 @@ -using Bit.App.Resources; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages diff --git a/src/App/Pages/Vault/SharePageViewModel.cs b/src/App/Pages/Vault/SharePageViewModel.cs index 0c8d0fd9d..c7e3cfbb1 100644 --- a/src/App/Pages/Vault/SharePageViewModel.cs +++ b/src/App/Pages/Vault/SharePageViewModel.cs @@ -1,13 +1,13 @@ -using Bit.App.Abstractions; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Bit.App.Pages { diff --git a/src/App/Pages/Vault/ViewPage.xaml.cs b/src/App/Pages/Vault/ViewPage.xaml.cs index 22e574e36..eb03551f1 100644 --- a/src/App/Pages/Vault/ViewPage.xaml.cs +++ b/src/App/Pages/Vault/ViewPage.xaml.cs @@ -1,9 +1,9 @@ using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Threading.Tasks; using Xamarin.Forms; namespace Bit.App.Pages @@ -216,7 +216,7 @@ namespace Bit.App.Pages return; } - var options = new List {AppResources.Attachments}; + var options = new List { AppResources.Attachments }; if (_vm.Cipher.OrganizationId == null) { options.Add(AppResources.Clone); diff --git a/src/App/Pages/Vault/ViewPageViewModel.cs b/src/App/Pages/Vault/ViewPageViewModel.cs index b47be00ab..9ad6f3b26 100644 --- a/src/App/Pages/Vault/ViewPageViewModel.cs +++ b/src/App/Pages/Vault/ViewPageViewModel.cs @@ -1,16 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Resources; using Bit.App.Utilities; +using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using Bit.Core; using Xamarin.Forms; namespace Bit.App.Pages @@ -233,7 +233,7 @@ namespace Bit.App.Pages set { SetProperty(ref _totpLow, value); - Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"]; + Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"]; } } public bool IsDeleted => Cipher.IsDeleted; @@ -285,7 +285,7 @@ namespace Bit.App.Pages public async void TogglePassword() { - if (! await PromptPasswordAsync()) + if (!await PromptPasswordAsync()) { return; } @@ -613,7 +613,7 @@ namespace Bit.App.Pages _attachmentData = null; _attachmentFilename = null; } - + private async void CopyAsync(string id, string text = null) { if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync()) diff --git a/src/App/Services/MobileI18nService.cs b/src/App/Services/MobileI18nService.cs index 24c70fcf2..da86ee4fe 100644 --- a/src/App/Services/MobileI18nService.cs +++ b/src/App/Services/MobileI18nService.cs @@ -1,11 +1,11 @@ -using Bit.App.Resources; -using Bit.Core.Abstractions; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Resources; using System.Threading; +using Bit.App.Resources; +using Bit.Core.Abstractions; namespace Bit.App.Services { diff --git a/src/App/Services/MobilePasswordRepromptService.cs b/src/App/Services/MobilePasswordRepromptService.cs index 8f1b7a04c..28a8e5a86 100644 --- a/src/App/Services/MobilePasswordRepromptService.cs +++ b/src/App/Services/MobilePasswordRepromptService.cs @@ -1,8 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; -using System; using Bit.Core.Utilities; namespace Bit.App.Services diff --git a/src/App/Services/MobilePlatformUtilsService.cs b/src/App/Services/MobilePlatformUtilsService.cs index f4e98e250..6b424dc66 100644 --- a/src/App/Services/MobilePlatformUtilsService.cs +++ b/src/App/Services/MobilePlatformUtilsService.cs @@ -14,7 +14,7 @@ using Xamarin.Forms; namespace Bit.App.Services { public class MobilePlatformUtilsService : IPlatformUtilsService - { + { private static readonly Random _random = new Random(); private const int DialogPromiseExpiration = 600000; // 10 minutes diff --git a/src/App/Services/MobileStorageService.cs b/src/App/Services/MobileStorageService.cs index 11694bf77..723048604 100644 --- a/src/App/Services/MobileStorageService.cs +++ b/src/App/Services/MobileStorageService.cs @@ -1,8 +1,8 @@ -using Bit.Core; -using Bit.Core.Abstractions; -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core; +using Bit.Core.Abstractions; namespace Bit.App.Services { diff --git a/src/App/Services/NoopPushNotificationListenerService.cs b/src/App/Services/NoopPushNotificationListenerService.cs index 7bb42b424..f15a258b5 100644 --- a/src/App/Services/NoopPushNotificationListenerService.cs +++ b/src/App/Services/NoopPushNotificationListenerService.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json.Linq; +using System.Threading.Tasks; using Bit.App.Abstractions; -using System.Threading.Tasks; +using Newtonsoft.Json.Linq; namespace Bit.App.Services { diff --git a/src/App/Services/PreferencesStorageService.cs b/src/App/Services/PreferencesStorageService.cs index ae0dc6609..1fdb72434 100644 --- a/src/App/Services/PreferencesStorageService.cs +++ b/src/App/Services/PreferencesStorageService.cs @@ -1,15 +1,15 @@ -using Bit.Core.Abstractions; +using System; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System; -using System.Threading.Tasks; namespace Bit.App.Services { public class PreferencesStorageService : IStorageService { public static string KeyFormat = "bwPreferencesStorage:{0}"; - + private readonly string _sharedName; private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { diff --git a/src/App/Services/PushNotificationListenerService.cs b/src/App/Services/PushNotificationListenerService.cs index 9c3154641..79521a7a5 100644 --- a/src/App/Services/PushNotificationListenerService.cs +++ b/src/App/Services/PushNotificationListenerService.cs @@ -1,17 +1,17 @@ -#if !FDROID +#if !FDROID +using System; using System.Diagnostics; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Response; +using Bit.Core.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Bit.App.Abstractions; -using System; using Xamarin.Forms; -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using System.Threading.Tasks; -using Bit.Core.Enums; -using Bit.Core; -using Bit.Core.Models.Response; -using Bit.Core.Exceptions; namespace Bit.App.Services { diff --git a/src/App/Services/SecureStorageService.cs b/src/App/Services/SecureStorageService.cs index 5655cc17e..1c1534b68 100644 --- a/src/App/Services/SecureStorageService.cs +++ b/src/App/Services/SecureStorageService.cs @@ -1,7 +1,7 @@ -using Bit.Core.Abstractions; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System.Threading.Tasks; namespace Bit.App.Services { diff --git a/src/App/Styles/Android.xaml.cs b/src/App/Styles/Android.xaml.cs index 166079cef..a2e9ecfd3 100644 --- a/src/App/Styles/Android.xaml.cs +++ b/src/App/Styles/Android.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Styles/Black.xaml.cs b/src/App/Styles/Black.xaml.cs index c13d1698f..b126f1bb5 100644 --- a/src/App/Styles/Black.xaml.cs +++ b/src/App/Styles/Black.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Styles/Dark.xaml.cs b/src/App/Styles/Dark.xaml.cs index b0bf3d949..1a4113ab6 100644 --- a/src/App/Styles/Dark.xaml.cs +++ b/src/App/Styles/Dark.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Styles/Light.xaml.cs b/src/App/Styles/Light.xaml.cs index f7defeec7..3ddad1482 100644 --- a/src/App/Styles/Light.xaml.cs +++ b/src/App/Styles/Light.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Styles/Nord.xaml.cs b/src/App/Styles/Nord.xaml.cs index 3ec5209f5..6baf44c3b 100644 --- a/src/App/Styles/Nord.xaml.cs +++ b/src/App/Styles/Nord.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Styles/Variables.xaml.cs b/src/App/Styles/Variables.xaml.cs index 214142bde..31582c54d 100644 --- a/src/App/Styles/Variables.xaml.cs +++ b/src/App/Styles/Variables.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Styles/iOS.xaml.cs b/src/App/Styles/iOS.xaml.cs index 72d94ad44..a08433b33 100644 --- a/src/App/Styles/iOS.xaml.cs +++ b/src/App/Styles/iOS.xaml.cs @@ -9,4 +9,4 @@ namespace Bit.App.Styles InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index d795fcf98..454d77021 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -1,21 +1,21 @@ using System; -using System.Net; -using Bit.App.Abstractions; -using Bit.App.Pages; -using Bit.App.Resources; -using Bit.Core.Abstractions; -using Bit.Core.Models.View; -using Bit.Core.Utilities; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Models; +using Bit.App.Pages; +using Bit.App.Resources; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; +using Bit.Core.Models.View; +using Bit.Core.Utilities; using Newtonsoft.Json; using Xamarin.Essentials; using Xamarin.Forms; @@ -279,7 +279,7 @@ namespace Bit.App.Utilities Subject = send.Name }); } - + private static string GetSendUrl(SendView send) { var environmentService = ServiceContainer.Resolve("environmentService"); @@ -465,12 +465,12 @@ namespace Bit.App.Utilities public static async Task IncrementInvalidUnlockAttemptsAsync() { var stateService = ServiceContainer.Resolve("stateService"); - var invalidUnlockAttempts = await stateService.GetInvalidUnlockAttemptsAsync(); + var invalidUnlockAttempts = await stateService.GetInvalidUnlockAttemptsAsync(); invalidUnlockAttempts++; await stateService.SetInvalidUnlockAttemptsAsync(invalidUnlockAttempts); return invalidUnlockAttempts; } - + public static async Task ResetInvalidUnlockAttemptsAsync() { var stateService = ServiceContainer.Resolve("stateService"); diff --git a/src/App/Utilities/I18nExtension.cs b/src/App/Utilities/I18nExtension.cs index 0cb6ed224..b66040c75 100644 --- a/src/App/Utilities/I18nExtension.cs +++ b/src/App/Utilities/I18nExtension.cs @@ -1,6 +1,6 @@ -using Bit.Core.Abstractions; +using System; +using Bit.Core.Abstractions; using Bit.Core.Utilities; -using System; using Xamarin.Forms; using Xamarin.Forms.Xaml; diff --git a/src/App/Utilities/PasswordFormatter.cs b/src/App/Utilities/PasswordFormatter.cs index 8824985ed..a6f25e8ea 100644 --- a/src/App/Utilities/PasswordFormatter.cs +++ b/src/App/Utilities/PasswordFormatter.cs @@ -42,7 +42,7 @@ namespace Bit.App.Utilities { result += "
"; } - + // Start with an otherwise uncovered case so we will definitely enter the "something changed" // state. var currentType = CharType.None; @@ -72,7 +72,7 @@ namespace Bit.App.Utilities { result += ""; } - + currentType = charType; // Switch the color if it is not a normal text. Otherwise leave the @@ -110,13 +110,13 @@ namespace Bit.App.Utilities { result += ""; } - + // Close off iOS div if (Device.RuntimePlatform == Device.iOS) { result += "
"; } - + return result; } } diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs index 782bbe5a2..48405d4e8 100644 --- a/src/Core/Abstractions/IApiService.cs +++ b/src/Core/Abstractions/IApiService.cs @@ -1,10 +1,10 @@ -using Bit.Core.Models.Domain; -using Bit.Core.Models.Request; -using Bit.Core.Models.Response; -using System; +using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using Bit.Core.Models.Domain; +using Bit.Core.Models.Request; +using Bit.Core.Models.Response; namespace Bit.Core.Abstractions { diff --git a/src/Core/Abstractions/IAuditService.cs b/src/Core/Abstractions/IAuditService.cs index f28bb2e6f..047fb7dad 100644 --- a/src/Core/Abstractions/IAuditService.cs +++ b/src/Core/Abstractions/IAuditService.cs @@ -9,4 +9,4 @@ namespace Bit.Core.Abstractions Task> BreachedAccountsAsync(string username); Task PasswordLeakedAsync(string password); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IBroadcasterService.cs b/src/Core/Abstractions/IBroadcasterService.cs index afdf29f21..ac481b53d 100644 --- a/src/Core/Abstractions/IBroadcasterService.cs +++ b/src/Core/Abstractions/IBroadcasterService.cs @@ -1,5 +1,5 @@ -using Bit.Core.Models.Domain; -using System; +using System; +using Bit.Core.Models.Domain; namespace Bit.Core.Abstractions { @@ -9,4 +9,4 @@ namespace Bit.Core.Abstractions void Subscribe(string id, Action messageCallback); void Unsubscribe(string id); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/ICipherService.cs b/src/Core/Abstractions/ICipherService.cs index f5902bf4a..60e4b722c 100644 --- a/src/Core/Abstractions/ICipherService.cs +++ b/src/Core/Abstractions/ICipherService.cs @@ -20,7 +20,7 @@ namespace Bit.Core.Abstractions Task EncryptAsync(CipherView model, SymmetricCryptoKey key = null, Cipher originalCipher = null); Task> GetAllAsync(); Task> GetAllDecryptedAsync(); - Task, List, List>> GetAllDecryptedByUrlAsync(string url, + Task, List, List>> GetAllDecryptedByUrlAsync(string url, List includeOtherTypes = null); Task> GetAllDecryptedForGroupingAsync(string groupingId, bool folder = true); Task> GetAllDecryptedForUrlAsync(string url); diff --git a/src/Core/Abstractions/ICryptoFunctionService.cs b/src/Core/Abstractions/ICryptoFunctionService.cs index 98e58bf48..14f8fdf6c 100644 --- a/src/Core/Abstractions/ICryptoFunctionService.cs +++ b/src/Core/Abstractions/ICryptoFunctionService.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; -using System; +using System; using System.Threading.Tasks; +using Bit.Core.Enums; namespace Bit.Core.Abstractions { diff --git a/src/Core/Abstractions/IEnvironmentService.cs b/src/Core/Abstractions/IEnvironmentService.cs index 06e76959a..7469452b9 100644 --- a/src/Core/Abstractions/IEnvironmentService.cs +++ b/src/Core/Abstractions/IEnvironmentService.cs @@ -18,4 +18,4 @@ namespace Bit.Core.Abstractions Task SetUrlsAsync(EnvironmentUrlData urls); Task SetUrlsFromStorageAsync(); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IExportService.cs b/src/Core/Abstractions/IExportService.cs index 022500c12..e43f98644 100644 --- a/src/Core/Abstractions/IExportService.cs +++ b/src/Core/Abstractions/IExportService.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Bit.Core.Abstractions { diff --git a/src/Core/Abstractions/IFileUploadService.cs b/src/Core/Abstractions/IFileUploadService.cs index 689b3440f..75f566550 100644 --- a/src/Core/Abstractions/IFileUploadService.cs +++ b/src/Core/Abstractions/IFileUploadService.cs @@ -1,9 +1,11 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; -namespace Bit.Core.Abstractions { - public interface IFileUploadService { +namespace Bit.Core.Abstractions +{ + public interface IFileUploadService + { Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData); Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData); } diff --git a/src/Core/Abstractions/IMessagingService.cs b/src/Core/Abstractions/IMessagingService.cs index 08d631907..5fb5f987d 100644 --- a/src/Core/Abstractions/IMessagingService.cs +++ b/src/Core/Abstractions/IMessagingService.cs @@ -4,4 +4,4 @@ { void Send(string subscriber, object arg = null); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/INativeLogService.cs b/src/Core/Abstractions/INativeLogService.cs index fdadcca37..c24a622ad 100644 --- a/src/Core/Abstractions/INativeLogService.cs +++ b/src/Core/Abstractions/INativeLogService.cs @@ -7,4 +7,4 @@ void Info(string message); void Warning(string message); } -} \ No newline at end of file +} diff --git a/src/Core/Abstractions/IPolicyService.cs b/src/Core/Abstractions/IPolicyService.cs index 2a2d3504a..d9dfb7263 100644 --- a/src/Core/Abstractions/IPolicyService.cs +++ b/src/Core/Abstractions/IPolicyService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Enums; diff --git a/src/Core/Abstractions/ISendService.cs b/src/Core/Abstractions/ISendService.cs index 91aa2ccae..c6015fa24 100644 --- a/src/Core/Abstractions/ISendService.cs +++ b/src/Core/Abstractions/ISendService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; diff --git a/src/Core/Abstractions/ITotpService.cs b/src/Core/Abstractions/ITotpService.cs index 0de407e3e..e0aff3c2f 100644 --- a/src/Core/Abstractions/ITotpService.cs +++ b/src/Core/Abstractions/ITotpService.cs @@ -8,4 +8,4 @@ namespace Bit.Core.Abstractions int GetTimeInterval(string key); Task IsAutoCopyEnabledAsync(); } -} \ No newline at end of file +} diff --git a/src/Core/BitwardenIcons.cs b/src/Core/BitwardenIcons.cs index c659d9bea..c621e9091 100644 --- a/src/Core/BitwardenIcons.cs +++ b/src/Core/BitwardenIcons.cs @@ -1,4 +1,4 @@ -namespace Bit.Core +namespace Bit.Core { public static class BitwardenIcons { diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index cfdec4eee..996358876 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -31,12 +31,12 @@ public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; public const int SaveFileRequestCode = 44; - + public static readonly string[] AndroidAllClearCipherCacheKeys = { ClearCiphersCacheKey }; - + public static readonly string[] iOSAllClearCipherCacheKeys = { ClearCiphersCacheKey, @@ -44,7 +44,7 @@ iOSExtensionClearCiphersCacheKey, iOSShareExtensionClearCiphersCacheKey }; - + public static string CiphersKey(string userId) => $"ciphers_{userId}"; public static string FoldersKey(string userId) => $"folders_{userId}"; public static string CollectionsKey(string userId) => $"collections_{userId}"; diff --git a/src/Core/Enums/ClientType.cs b/src/Core/Enums/ClientType.cs index f6d3fd9c2..9d5e4feab 100644 --- a/src/Core/Enums/ClientType.cs +++ b/src/Core/Enums/ClientType.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum ClientType: byte + public enum ClientType : byte { Web = 1, Browser = 2, diff --git a/src/Core/Enums/HdkfAlgorithm.cs b/src/Core/Enums/HdkfAlgorithm.cs index 5837a3fa3..0f2b32e17 100644 --- a/src/Core/Enums/HdkfAlgorithm.cs +++ b/src/Core/Enums/HdkfAlgorithm.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Enums +namespace Bit.Core.Enums { public enum HkdfAlgorithm : byte { diff --git a/src/Core/Enums/LinkedIdType.cs b/src/Core/Enums/LinkedIdType.cs index 32803868c..2ffd0acb5 100644 --- a/src/Core/Enums/LinkedIdType.cs +++ b/src/Core/Enums/LinkedIdType.cs @@ -1,6 +1,7 @@ -namespace Bit.Core.Enums { - - public enum LinkedIdType: int +namespace Bit.Core.Enums +{ + + public enum LinkedIdType : int { // Login Login_Username = 100, diff --git a/src/Core/Enums/SendType.cs b/src/Core/Enums/SendType.cs index f8f06f13d..fb447b2b5 100644 --- a/src/Core/Enums/SendType.cs +++ b/src/Core/Enums/SendType.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Enums +namespace Bit.Core.Enums { public enum SendType { diff --git a/src/Core/Exceptions/ApiException.cs b/src/Core/Exceptions/ApiException.cs index bf0aa9791..493900137 100644 --- a/src/Core/Exceptions/ApiException.cs +++ b/src/Core/Exceptions/ApiException.cs @@ -1,5 +1,5 @@ -using Bit.Core.Models.Response; -using System; +using System; +using Bit.Core.Models.Response; namespace Bit.Core.Exceptions { diff --git a/src/Core/Models/Api/SendFileApi.cs b/src/Core/Models/Api/SendFileApi.cs index ae7f01aea..345d9f215 100644 --- a/src/Core/Models/Api/SendFileApi.cs +++ b/src/Core/Models/Api/SendFileApi.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Api +namespace Bit.Core.Models.Api { public class SendFileApi { diff --git a/src/Core/Models/Api/SendTextApi.cs b/src/Core/Models/Api/SendTextApi.cs index 9520031da..2b7b92429 100644 --- a/src/Core/Models/Api/SendTextApi.cs +++ b/src/Core/Models/Api/SendTextApi.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Api +namespace Bit.Core.Models.Api { public class SendTextApi { diff --git a/src/Core/Models/Data/CipherData.cs b/src/Core/Models/Data/CipherData.cs index 50cb20133..c571771a7 100644 --- a/src/Core/Models/Data/CipherData.cs +++ b/src/Core/Models/Data/CipherData.cs @@ -1,8 +1,8 @@ -using Bit.Core.Models.Response; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; +using Bit.Core.Models.Response; namespace Bit.Core.Models.Data { diff --git a/src/Core/Models/Data/EventData.cs b/src/Core/Models/Data/EventData.cs index 49f0957f3..6a5007033 100644 --- a/src/Core/Models/Data/EventData.cs +++ b/src/Core/Models/Data/EventData.cs @@ -1,5 +1,5 @@ -using Bit.Core.Enums; -using System; +using System; +using Bit.Core.Enums; namespace Bit.Core.Models.Data { diff --git a/src/Core/Models/Data/FolderData.cs b/src/Core/Models/Data/FolderData.cs index 24a155a05..75e56d16e 100644 --- a/src/Core/Models/Data/FolderData.cs +++ b/src/Core/Models/Data/FolderData.cs @@ -1,5 +1,5 @@ -using Bit.Core.Models.Response; -using System; +using System; +using Bit.Core.Models.Response; namespace Bit.Core.Models.Data { diff --git a/src/Core/Models/Data/LoginData.cs b/src/Core/Models/Data/LoginData.cs index 7afff7e36..286c542ed 100644 --- a/src/Core/Models/Data/LoginData.cs +++ b/src/Core/Models/Data/LoginData.cs @@ -1,7 +1,7 @@ -using Bit.Core.Models.Api; -using System; +using System; using System.Collections.Generic; using System.Linq; +using Bit.Core.Models.Api; namespace Bit.Core.Models.Data { diff --git a/src/Core/Models/Data/PasswordHistoryData.cs b/src/Core/Models/Data/PasswordHistoryData.cs index 5d15bc0a7..9ade26ef2 100644 --- a/src/Core/Models/Data/PasswordHistoryData.cs +++ b/src/Core/Models/Data/PasswordHistoryData.cs @@ -1,5 +1,5 @@ -using Bit.Core.Models.Response; -using System; +using System; +using Bit.Core.Models.Response; namespace Bit.Core.Models.Data { diff --git a/src/Core/Models/Data/Permissions.cs b/src/Core/Models/Data/Permissions.cs index 4c6e7fe3a..c1b8278f2 100644 --- a/src/Core/Models/Data/Permissions.cs +++ b/src/Core/Models/Data/Permissions.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Data +namespace Bit.Core.Models.Data { public class Permissions { diff --git a/src/Core/Models/Data/SendData.cs b/src/Core/Models/Data/SendData.cs index ce891b3cc..a4659c37f 100644 --- a/src/Core/Models/Data/SendData.cs +++ b/src/Core/Models/Data/SendData.cs @@ -1,4 +1,4 @@ -using System; +using System; using Bit.Core.Enums; using Bit.Core.Models.Response; @@ -38,7 +38,7 @@ namespace Bit.Core.Models.Data break; } } - + public string Id { get; set; } public string AccessId { get; set; } public string UserId { get; set; } diff --git a/src/Core/Models/Data/SendFileData.cs b/src/Core/Models/Data/SendFileData.cs index 65027d6a5..d596bde64 100644 --- a/src/Core/Models/Data/SendFileData.cs +++ b/src/Core/Models/Data/SendFileData.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using System.Drawing; using Bit.Core.Models.Api; namespace Bit.Core.Models.Data diff --git a/src/Core/Models/Data/SendTextData.cs b/src/Core/Models/Data/SendTextData.cs index 2e83ca1a0..ae4f4abfc 100644 --- a/src/Core/Models/Data/SendTextData.cs +++ b/src/Core/Models/Data/SendTextData.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using System.Drawing; using Bit.Core.Models.Api; namespace Bit.Core.Models.Data diff --git a/src/Core/Models/Domain/Attachment.cs b/src/Core/Models/Domain/Attachment.cs index b46f66a31..3e71d6107 100644 --- a/src/Core/Models/Domain/Attachment.cs +++ b/src/Core/Models/Domain/Attachment.cs @@ -1,9 +1,9 @@ -using Bit.Core.Abstractions; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System.Collections.Generic; -using System.Threading.Tasks; namespace Bit.Core.Models.Domain { @@ -39,7 +39,7 @@ namespace Bit.Core.Models.Domain { "FileName" }, orgId); - + if (Key != null) { var cryptoService = ServiceContainer.Resolve("cryptoService"); diff --git a/src/Core/Models/Domain/AuthResult.cs b/src/Core/Models/Domain/AuthResult.cs index 404cea660..8e48c7da7 100644 --- a/src/Core/Models/Domain/AuthResult.cs +++ b/src/Core/Models/Domain/AuthResult.cs @@ -1,5 +1,5 @@ -using Bit.Core.Enums; -using System.Collections.Generic; +using System.Collections.Generic; +using Bit.Core.Enums; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Card.cs b/src/Core/Models/Domain/Card.cs index b8c96da02..8a7ffff23 100644 --- a/src/Core/Models/Domain/Card.cs +++ b/src/Core/Models/Domain/Card.cs @@ -1,7 +1,7 @@ -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Cipher.cs b/src/Core/Models/Domain/Cipher.cs index cf37b6c0b..d744f6a57 100644 --- a/src/Core/Models/Domain/Cipher.cs +++ b/src/Core/Models/Domain/Cipher.cs @@ -1,10 +1,10 @@ -using Bit.Core.Enums; -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Collection.cs b/src/Core/Models/Domain/Collection.cs index 4fd4d5551..da62c55e2 100644 --- a/src/Core/Models/Domain/Collection.cs +++ b/src/Core/Models/Domain/Collection.cs @@ -1,7 +1,7 @@ -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/EncByteArray.cs b/src/Core/Models/Domain/EncByteArray.cs index a3214cad3..97814a7aa 100644 --- a/src/Core/Models/Domain/EncByteArray.cs +++ b/src/Core/Models/Domain/EncByteArray.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Domain +namespace Bit.Core.Models.Domain { public class EncByteArray { diff --git a/src/Core/Models/Domain/EncString.cs b/src/Core/Models/Domain/EncString.cs index df4757b9e..2a86348a6 100644 --- a/src/Core/Models/Domain/EncString.cs +++ b/src/Core/Models/Domain/EncString.cs @@ -1,8 +1,8 @@ -using Bit.Core.Abstractions; +using System; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Field.cs b/src/Core/Models/Domain/Field.cs index 31f5d4340..cbfdcbca3 100644 --- a/src/Core/Models/Domain/Field.cs +++ b/src/Core/Models/Domain/Field.cs @@ -1,8 +1,8 @@ -using Bit.Core.Enums; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.View; -using System.Collections.Generic; -using System.Threading.Tasks; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Folder.cs b/src/Core/Models/Domain/Folder.cs index 8b1b9429e..fe59ef5f0 100644 --- a/src/Core/Models/Domain/Folder.cs +++ b/src/Core/Models/Domain/Folder.cs @@ -1,8 +1,8 @@ -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Identity.cs b/src/Core/Models/Domain/Identity.cs index edc5869cb..4a705dcb6 100644 --- a/src/Core/Models/Domain/Identity.cs +++ b/src/Core/Models/Domain/Identity.cs @@ -1,7 +1,7 @@ -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Login.cs b/src/Core/Models/Domain/Login.cs index abc9ead94..00c0ab07b 100644 --- a/src/Core/Models/Domain/Login.cs +++ b/src/Core/Models/Domain/Login.cs @@ -1,9 +1,9 @@ -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/LoginUri.cs b/src/Core/Models/Domain/LoginUri.cs index 7acba40b5..31747fd96 100644 --- a/src/Core/Models/Domain/LoginUri.cs +++ b/src/Core/Models/Domain/LoginUri.cs @@ -1,8 +1,8 @@ -using Bit.Core.Enums; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.View; -using System.Collections.Generic; -using System.Threading.Tasks; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/PasswordHistory.cs b/src/Core/Models/Domain/PasswordHistory.cs index b9d1e9ef0..6ae1ab1f1 100644 --- a/src/Core/Models/Domain/PasswordHistory.cs +++ b/src/Core/Models/Domain/PasswordHistory.cs @@ -1,8 +1,8 @@ -using Bit.Core.Models.Data; -using Bit.Core.Models.View; -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Bit.Core.Models.View; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Policy.cs b/src/Core/Models/Domain/Policy.cs index 470c92855..a84b095d0 100644 --- a/src/Core/Models/Domain/Policy.cs +++ b/src/Core/Models/Domain/Policy.cs @@ -16,7 +16,7 @@ namespace Bit.Core.Models.Domain Data = obj.Data; Enabled = obj.Enabled; } - + public string Id { get; set; } public string OrganizationId { get; set; } public PolicyType Type { get; set; } diff --git a/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs b/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs index 1653aa774..4e9cebe2e 100644 --- a/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs +++ b/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Domain +namespace Bit.Core.Models.Domain { public class ResetPasswordPolicyOptions { diff --git a/src/Core/Models/Domain/SecureNote.cs b/src/Core/Models/Domain/SecureNote.cs index 6d3afab4c..55817ab15 100644 --- a/src/Core/Models/Domain/SecureNote.cs +++ b/src/Core/Models/Domain/SecureNote.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; +using System.Threading.Tasks; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.View; -using System.Threading.Tasks; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Domain/Send.cs b/src/Core/Models/Domain/Send.cs index 2c7761d41..4ecea0c15 100644 --- a/src/Core/Models/Domain/Send.cs +++ b/src/Core/Models/Domain/Send.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Abstractions; diff --git a/src/Core/Models/Domain/SendFile.cs b/src/Core/Models/Domain/SendFile.cs index 77b24f3f6..cde9086d1 100644 --- a/src/Core/Models/Domain/SendFile.cs +++ b/src/Core/Models/Domain/SendFile.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Models.Data; using Bit.Core.Models.View; diff --git a/src/Core/Models/Domain/SendText.cs b/src/Core/Models/Domain/SendText.cs index f7825343e..adff8251f 100644 --- a/src/Core/Models/Domain/SendText.cs +++ b/src/Core/Models/Domain/SendText.cs @@ -1,4 +1,4 @@ - + using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Models.Data; diff --git a/src/Core/Models/Domain/SymmetricCryptoKey.cs b/src/Core/Models/Domain/SymmetricCryptoKey.cs index 820823b26..91bce7550 100644 --- a/src/Core/Models/Domain/SymmetricCryptoKey.cs +++ b/src/Core/Models/Domain/SymmetricCryptoKey.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; -using System; +using System; using System.Linq; +using Bit.Core.Enums; namespace Bit.Core.Models.Domain { diff --git a/src/Core/Models/Export/CollectionWithId.cs b/src/Core/Models/Export/CollectionWithId.cs index 2b8af4dd2..01e80f250 100644 --- a/src/Core/Models/Export/CollectionWithId.cs +++ b/src/Core/Models/Export/CollectionWithId.cs @@ -10,7 +10,7 @@ namespace Bit.Core.Models.Export Id = obj.Id; } - public CollectionWithId(Domain.Collection obj): base(obj) + public CollectionWithId(Domain.Collection obj) : base(obj) { Id = obj.Id; } diff --git a/src/Core/Models/Request/CipherCreateRequest.cs b/src/Core/Models/Request/CipherCreateRequest.cs index aefb9b340..bee9b3c64 100644 --- a/src/Core/Models/Request/CipherCreateRequest.cs +++ b/src/Core/Models/Request/CipherCreateRequest.cs @@ -1,6 +1,6 @@ -using Bit.Core.Models.Domain; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.Request { diff --git a/src/Core/Models/Request/CipherRequest.cs b/src/Core/Models/Request/CipherRequest.cs index ca00ee182..82c029751 100644 --- a/src/Core/Models/Request/CipherRequest.cs +++ b/src/Core/Models/Request/CipherRequest.cs @@ -1,9 +1,9 @@ -using Bit.Core.Enums; -using Bit.Core.Models.Api; -using Bit.Core.Models.Domain; +using System; using System.Collections.Generic; using System.Linq; -using System; +using Bit.Core.Enums; +using Bit.Core.Models.Api; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.Request { diff --git a/src/Core/Models/Request/CipherShareRequest.cs b/src/Core/Models/Request/CipherShareRequest.cs index 4fb4f3761..89bfb6dc6 100644 --- a/src/Core/Models/Request/CipherShareRequest.cs +++ b/src/Core/Models/Request/CipherShareRequest.cs @@ -1,6 +1,6 @@ -using Bit.Core.Models.Domain; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.Request { diff --git a/src/Core/Models/Request/EventRequest.cs b/src/Core/Models/Request/EventRequest.cs index d1c728287..da4124189 100644 --- a/src/Core/Models/Request/EventRequest.cs +++ b/src/Core/Models/Request/EventRequest.cs @@ -1,5 +1,5 @@ -using Bit.Core.Enums; -using System; +using System; +using Bit.Core.Enums; namespace Bit.Core.Models.Request { diff --git a/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs b/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs index b751a9a4f..267a425ec 100644 --- a/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs +++ b/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Request +namespace Bit.Core.Models.Request { public class OrganizationUserResetPasswordEnrollmentRequest { diff --git a/src/Core/Models/Request/SendRequest.cs b/src/Core/Models/Request/SendRequest.cs index fb3d643a9..abd7323ec 100644 --- a/src/Core/Models/Request/SendRequest.cs +++ b/src/Core/Models/Request/SendRequest.cs @@ -1,4 +1,4 @@ -using System; +using System; using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Domain; @@ -23,7 +23,7 @@ namespace Bit.Core.Models.Request public SendRequest(Send send, long? fileLength) { - Type = send.Type ; + Type = send.Type; FileLength = fileLength; Name = send.Name?.EncryptedString; Notes = send.Notes?.EncryptedString; diff --git a/src/Core/Models/Request/TokenRequest.cs b/src/Core/Models/Request/TokenRequest.cs index b982c2ea4..fe299e455 100644 --- a/src/Core/Models/Request/TokenRequest.cs +++ b/src/Core/Models/Request/TokenRequest.cs @@ -1,9 +1,9 @@ -using Bit.Core.Enums; -using Bit.Core.Utilities; -using System; +using System; using System.Collections.Generic; using System.Net.Http.Headers; using System.Text; +using Bit.Core.Enums; +using Bit.Core.Utilities; namespace Bit.Core.Models.Request { diff --git a/src/Core/Models/Response/AttachmentUploadDataReponse.cs b/src/Core/Models/Response/AttachmentUploadDataReponse.cs index 5f1a76c37..e26059d9f 100644 --- a/src/Core/Models/Response/AttachmentUploadDataReponse.cs +++ b/src/Core/Models/Response/AttachmentUploadDataReponse.cs @@ -1,4 +1,4 @@ -using Bit.Core.Enums; +using Bit.Core.Enums; namespace Bit.Core.Models.Response { diff --git a/src/Core/Models/Response/CipherResponse.cs b/src/Core/Models/Response/CipherResponse.cs index 684d5f82d..070d41eee 100644 --- a/src/Core/Models/Response/CipherResponse.cs +++ b/src/Core/Models/Response/CipherResponse.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; -using Bit.Core.Models.Api; -using System; +using System; using System.Collections.Generic; +using Bit.Core.Enums; +using Bit.Core.Models.Api; namespace Bit.Core.Models.Response { diff --git a/src/Core/Models/Response/ErrorResponse.cs b/src/Core/Models/Response/ErrorResponse.cs index cd240e44c..71cde4317 100644 --- a/src/Core/Models/Response/ErrorResponse.cs +++ b/src/Core/Models/Response/ErrorResponse.cs @@ -1,8 +1,8 @@ -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; +using Newtonsoft.Json.Linq; namespace Bit.Core.Models.Response { diff --git a/src/Core/Models/Response/IdentityCaptchaResponse.cs b/src/Core/Models/Response/IdentityCaptchaResponse.cs index 018248b94..a5350e8da 100644 --- a/src/Core/Models/Response/IdentityCaptchaResponse.cs +++ b/src/Core/Models/Response/IdentityCaptchaResponse.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; +using System.Collections.Generic; +using Bit.Core.Enums; using Newtonsoft.Json; -using System.Collections.Generic; namespace Bit.Core.Models.Response { diff --git a/src/Core/Models/Response/IdentityResponse.cs b/src/Core/Models/Response/IdentityResponse.cs index 6f693e571..cb843fd8d 100644 --- a/src/Core/Models/Response/IdentityResponse.cs +++ b/src/Core/Models/Response/IdentityResponse.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using Newtonsoft.Json.Linq; namespace Bit.Core.Models.Response diff --git a/src/Core/Models/Response/IdentityTwoFactorResponse.cs b/src/Core/Models/Response/IdentityTwoFactorResponse.cs index 65b1f94b8..928bd4303 100644 --- a/src/Core/Models/Response/IdentityTwoFactorResponse.cs +++ b/src/Core/Models/Response/IdentityTwoFactorResponse.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; +using System.Collections.Generic; +using Bit.Core.Enums; using Newtonsoft.Json; -using System.Collections.Generic; namespace Bit.Core.Models.Response { diff --git a/src/Core/Models/Response/NotificationResponse.cs b/src/Core/Models/Response/NotificationResponse.cs index 8e37b2673..c32812e46 100644 --- a/src/Core/Models/Response/NotificationResponse.cs +++ b/src/Core/Models/Response/NotificationResponse.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; -using System; +using System; using System.Collections.Generic; +using Bit.Core.Enums; namespace Bit.Core.Models.Response { diff --git a/src/Core/Models/Response/OrganizationAutoEnrollStatusResponse.cs b/src/Core/Models/Response/OrganizationAutoEnrollStatusResponse.cs index c766a82ff..d354f8ae5 100644 --- a/src/Core/Models/Response/OrganizationAutoEnrollStatusResponse.cs +++ b/src/Core/Models/Response/OrganizationAutoEnrollStatusResponse.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Response +namespace Bit.Core.Models.Response { public class OrganizationAutoEnrollStatusResponse { diff --git a/src/Core/Models/Response/OrganizationKeysResponse.cs b/src/Core/Models/Response/OrganizationKeysResponse.cs index 28d350f7b..1f9c7aac5 100644 --- a/src/Core/Models/Response/OrganizationKeysResponse.cs +++ b/src/Core/Models/Response/OrganizationKeysResponse.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Models.Response +namespace Bit.Core.Models.Response { public class OrganizationKeysResponse { diff --git a/src/Core/Models/Response/SendResponse.cs b/src/Core/Models/Response/SendResponse.cs index ca6a3bc84..89ee96598 100644 --- a/src/Core/Models/Response/SendResponse.cs +++ b/src/Core/Models/Response/SendResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; using Bit.Core.Enums; using Bit.Core.Models.Api; diff --git a/src/Core/Models/View/CardView.cs b/src/Core/Models/View/CardView.cs index 421d0b712..6b88d01e3 100644 --- a/src/Core/Models/View/CardView.cs +++ b/src/Core/Models/View/CardView.cs @@ -1,7 +1,7 @@ -using Bit.Core.Models.Domain; -using Bit.Core.Enums; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.RegularExpressions; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/CipherView.cs b/src/Core/Models/View/CipherView.cs index 1d238f271..6bec74ee4 100644 --- a/src/Core/Models/View/CipherView.cs +++ b/src/Core/Models/View/CipherView.cs @@ -1,8 +1,8 @@ -using Bit.Core.Enums; -using Bit.Core.Models.Domain; -using System; +using System; using System.Collections.Generic; using System.Linq; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/FolderView.cs b/src/Core/Models/View/FolderView.cs index e09eea1d3..f20f91107 100644 --- a/src/Core/Models/View/FolderView.cs +++ b/src/Core/Models/View/FolderView.cs @@ -1,5 +1,5 @@ -using Bit.Core.Models.Domain; -using System; +using System; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/IdentityView.cs b/src/Core/Models/View/IdentityView.cs index d4dae2922..b21a5b927 100644 --- a/src/Core/Models/View/IdentityView.cs +++ b/src/Core/Models/View/IdentityView.cs @@ -1,6 +1,6 @@ -using Bit.Core.Models.Domain; +using System.Collections.Generic; using Bit.Core.Enums; -using System.Collections.Generic; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/ItemView.cs b/src/Core/Models/View/ItemView.cs index e0e6fba13..a264cf441 100644 --- a/src/Core/Models/View/ItemView.cs +++ b/src/Core/Models/View/ItemView.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Bit.Core.Enums; namespace Bit.Core.Models.View diff --git a/src/Core/Models/View/LoginUriView.cs b/src/Core/Models/View/LoginUriView.cs index 74440fbaa..62ca2dd39 100644 --- a/src/Core/Models/View/LoginUriView.cs +++ b/src/Core/Models/View/LoginUriView.cs @@ -1,9 +1,9 @@ -using Bit.Core.Enums; -using Bit.Core.Models.Domain; -using Bit.Core.Utilities; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; +using Bit.Core.Utilities; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/LoginView.cs b/src/Core/Models/View/LoginView.cs index 595c69928..850082115 100644 --- a/src/Core/Models/View/LoginView.cs +++ b/src/Core/Models/View/LoginView.cs @@ -1,8 +1,8 @@ -using Bit.Core.Models.Domain; -using Bit.Core.Enums; -using System; +using System; using System.Collections.Generic; using System.Linq; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/PasswordHistoryView.cs b/src/Core/Models/View/PasswordHistoryView.cs index 03d646730..0d3472f52 100644 --- a/src/Core/Models/View/PasswordHistoryView.cs +++ b/src/Core/Models/View/PasswordHistoryView.cs @@ -1,5 +1,5 @@ -using Bit.Core.Models.Domain; -using System; +using System; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { @@ -11,7 +11,7 @@ namespace Bit.Core.Models.View { LastUsedDate = ph.LastUsedDate; } - + public string Password { get; set; } public DateTime LastUsedDate { get; set; } } diff --git a/src/Core/Models/View/SecureNoteView.cs b/src/Core/Models/View/SecureNoteView.cs index 67f3818df..88a02fcbe 100644 --- a/src/Core/Models/View/SecureNoteView.cs +++ b/src/Core/Models/View/SecureNoteView.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; +using System.Collections.Generic; +using Bit.Core.Enums; using Bit.Core.Models.Domain; -using System.Collections.Generic; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/SendFileView.cs b/src/Core/Models/View/SendFileView.cs index 840aa04af..3b1d20ac9 100644 --- a/src/Core/Models/View/SendFileView.cs +++ b/src/Core/Models/View/SendFileView.cs @@ -1,4 +1,4 @@ -using System.Dynamic; +using System.Dynamic; using Bit.Core.Models.Domain; namespace Bit.Core.Models.View diff --git a/src/Core/Models/View/SendTextView.cs b/src/Core/Models/View/SendTextView.cs index f61a8166c..2e391a452 100644 --- a/src/Core/Models/View/SendTextView.cs +++ b/src/Core/Models/View/SendTextView.cs @@ -1,4 +1,4 @@ -using Bit.Core.Models.Domain; +using Bit.Core.Models.Domain; namespace Bit.Core.Models.View { diff --git a/src/Core/Models/View/SendView.cs b/src/Core/Models/View/SendView.cs index dcc5264e9..259eea531 100644 --- a/src/Core/Models/View/SendView.cs +++ b/src/Core/Models/View/SendView.cs @@ -1,4 +1,4 @@ -using System; +using System; using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Utilities; diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index 78853c233..fdd880c59 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -1,4 +1,10 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; @@ -7,12 +13,6 @@ using Bit.Core.Models.Response; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; namespace Bit.Core.Services { @@ -202,7 +202,7 @@ namespace Bit.Core.Services { return SendAsync(HttpMethod.Delete, "/accounts", request, true, false); } - + public Task PostConvertToKeyConnector() { return SendAsync(HttpMethod.Post, "/accounts/convert-to-key-connector", null, true, false); @@ -438,9 +438,9 @@ namespace Bit.Core.Services } #endregion - + #region Organizations APIs - + public Task GetOrganizationKeysAsync(string id) { return SendAsync(HttpMethod.Get, $"/organizations/{id}/keys", null, true, true); @@ -556,7 +556,7 @@ namespace Bit.Core.Services requestMessage.Method = HttpMethod.Get; requestMessage.RequestUri = new Uri(string.Concat(IdentityBaseUrl, path)); requestMessage.Headers.Add("Accept", "application/json"); - + HttpResponseMessage response; try { diff --git a/src/Core/Services/AppIdService.cs b/src/Core/Services/AppIdService.cs index ada27d857..b95fd432f 100644 --- a/src/Core/Services/AppIdService.cs +++ b/src/Core/Services/AppIdService.cs @@ -1,6 +1,6 @@ -using Bit.Core.Abstractions; -using System; +using System; using System.Threading.Tasks; +using Bit.Core.Abstractions; namespace Bit.Core.Services { diff --git a/src/Core/Services/AuditService.cs b/src/Core/Services/AuditService.cs index 67d138672..d6543e9f3 100644 --- a/src/Core/Services/AuditService.cs +++ b/src/Core/Services/AuditService.cs @@ -1,11 +1,11 @@ -using Bit.Core.Abstractions; -using Bit.Core.Exceptions; -using Bit.Core.Models.Response; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Exceptions; +using Bit.Core.Models.Response; namespace Bit.Core.Services { diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 4d043f39b..3e6517f82 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -1,11 +1,11 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; namespace Bit.Core.Services { diff --git a/src/Core/Services/AzureFileUploadService.cs b/src/Core/Services/AzureFileUploadService.cs index 9f39ed8b8..4d18c1246 100644 --- a/src/Core/Services/AzureFileUploadService.cs +++ b/src/Core/Services/AzureFileUploadService.cs @@ -80,63 +80,63 @@ namespace Bit.Core.Services throw new Exception($"Cannot upload file, exceeds maximum size of {blockSize * MAX_BLOCKS_PER_BLOB}"); } - while (blockIndex < numBlocks) - { - uri = await RenewUriIfNecessary(uri, renewalFunc); - var blockUriBuilder = new UriBuilder(uri); - var blockId = EncodeBlockId(blockIndex); - var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query); - blockParams.Add("comp", "block"); - blockParams.Add("blockid", blockId); - blockUriBuilder.Query = blockParams.ToString(); - - using (var requestMessage = new HttpRequestMessage()) - { - requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R")); - requestMessage.Headers.Add("x-ms-version", baseParams["sv"]); - requestMessage.Headers.Add("x-ms-blob-type", "BlockBlob"); - - requestMessage.Content = new ByteArrayContent(data.Buffer.Skip(blockIndex * blockSize).Take(blockSize).ToArray()); - requestMessage.Version = new Version(1, 0); - requestMessage.Method = HttpMethod.Put; - requestMessage.RequestUri = blockUriBuilder.Uri; - - var blockResponse = await _httpClient.SendAsync(requestMessage); - - if (blockResponse.StatusCode != HttpStatusCode.Created) - { - throw new Exception("Failed to create Azure block"); - } - } - - blocksStaged.Add(blockId); - blockIndex++; - } + while (blockIndex < numBlocks) + { + uri = await RenewUriIfNecessary(uri, renewalFunc); + var blockUriBuilder = new UriBuilder(uri); + var blockId = EncodeBlockId(blockIndex); + var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query); + blockParams.Add("comp", "block"); + blockParams.Add("blockid", blockId); + blockUriBuilder.Query = blockParams.ToString(); using (var requestMessage = new HttpRequestMessage()) { - uri = await RenewUriIfNecessary(uri, renewalFunc); - var blockListXml = GenerateBlockListXml(blocksStaged); - var blockListUriBuilder = new UriBuilder(uri); - var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query); - blockListParams.Add("comp", "blocklist"); - blockListUriBuilder.Query = blockListParams.ToString(); - requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R")); requestMessage.Headers.Add("x-ms-version", baseParams["sv"]); + requestMessage.Headers.Add("x-ms-blob-type", "BlockBlob"); - requestMessage.Content = new StringContent(blockListXml); + requestMessage.Content = new ByteArrayContent(data.Buffer.Skip(blockIndex * blockSize).Take(blockSize).ToArray()); requestMessage.Version = new Version(1, 0); requestMessage.Method = HttpMethod.Put; - requestMessage.RequestUri = blockListUriBuilder.Uri; + requestMessage.RequestUri = blockUriBuilder.Uri; - var blockListResponse = await _httpClient.SendAsync(requestMessage); + var blockResponse = await _httpClient.SendAsync(requestMessage); - if (blockListResponse.StatusCode != HttpStatusCode.Created) + if (blockResponse.StatusCode != HttpStatusCode.Created) { - throw new Exception("Failed to PUT Azure block list"); + throw new Exception("Failed to create Azure block"); } } + + blocksStaged.Add(blockId); + blockIndex++; + } + + using (var requestMessage = new HttpRequestMessage()) + { + uri = await RenewUriIfNecessary(uri, renewalFunc); + var blockListXml = GenerateBlockListXml(blocksStaged); + var blockListUriBuilder = new UriBuilder(uri); + var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query); + blockListParams.Add("comp", "blocklist"); + blockListUriBuilder.Query = blockListParams.ToString(); + + requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R")); + requestMessage.Headers.Add("x-ms-version", baseParams["sv"]); + + requestMessage.Content = new StringContent(blockListXml); + requestMessage.Version = new Version(1, 0); + requestMessage.Method = HttpMethod.Put; + requestMessage.RequestUri = blockListUriBuilder.Uri; + + var blockListResponse = await _httpClient.SendAsync(requestMessage); + + if (blockListResponse.StatusCode != HttpStatusCode.Created) + { + throw new Exception("Failed to PUT Azure block list"); + } + } } private async Task RenewUriIfNecessary(string uri, Func> renewalFunc) @@ -153,7 +153,7 @@ namespace Bit.Core.Services private string GenerateBlockListXml(List blocksStaged) { var xml = new StringBuilder(""); - foreach(var blockId in blocksStaged) + foreach (var blockId in blocksStaged) { xml.Append($"{blockId}"); } @@ -180,7 +180,7 @@ namespace Bit.Core.Services maxSize = 104857600L; // 100 MiB } - return maxSize > MAX_MOBILE_BLOCK_SIZE ? (int)MAX_MOBILE_BLOCK_SIZE : (int) maxSize; + return maxSize > MAX_MOBILE_BLOCK_SIZE ? (int)MAX_MOBILE_BLOCK_SIZE : (int)maxSize; } private int CompareAzureVersions(string a, string b) diff --git a/src/Core/Services/BitwardenFileUploadService.cs b/src/Core/Services/BitwardenFileUploadService.cs index 52df1a179..88295c0a9 100644 --- a/src/Core/Services/BitwardenFileUploadService.cs +++ b/src/Core/Services/BitwardenFileUploadService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; using Bit.Core.Models.Domain; diff --git a/src/Core/Services/BroadcasterService.cs b/src/Core/Services/BroadcasterService.cs index 09079fb33..553ec49c9 100644 --- a/src/Core/Services/BroadcasterService.cs +++ b/src/Core/Services/BroadcasterService.cs @@ -1,8 +1,8 @@ -using Bit.Core.Abstractions; -using Bit.Core.Models.Domain; -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Models.Domain; namespace Bit.App.Services { diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index e39f5cb6d..bf48e80d8 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -1,4 +1,11 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; @@ -7,13 +14,6 @@ using Bit.Core.Models.Request; using Bit.Core.Models.Response; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Bit.Core.Services { @@ -48,7 +48,7 @@ namespace Bit.Core.Services IStorageService storageService, II18nService i18nService, Func searchService, - string clearCipherCacheKey, + string clearCipherCacheKey, string[] allClearCipherCacheKeys) { _cryptoService = cryptoService; diff --git a/src/Core/Services/CollectionService.cs b/src/Core/Services/CollectionService.cs index ddd7cfb12..fe30159dc 100644 --- a/src/Core/Services/CollectionService.cs +++ b/src/Core/Services/CollectionService.cs @@ -1,13 +1,13 @@ -using Bit.Core.Abstractions; -using Bit.Core.Models.Data; -using Bit.Core.Models.Domain; -using Bit.Core.Models.View; -using Bit.Core.Utilities; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; +using Bit.Core.Models.View; +using Bit.Core.Utilities; namespace Bit.Core.Services { diff --git a/src/Core/Services/ConsoleLogService.cs b/src/Core/Services/ConsoleLogService.cs index 492891834..1814d48c6 100644 --- a/src/Core/Services/ConsoleLogService.cs +++ b/src/Core/Services/ConsoleLogService.cs @@ -1,5 +1,5 @@ -using Bit.Core.Abstractions; -using System; +using System; +using Bit.Core.Abstractions; namespace Bit.Core.Services { diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 3b3318be7..5cd5312eb 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -1,13 +1,13 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Numerics; using Bit.Core.Utilities; namespace Bit.Core.Services diff --git a/src/Core/Services/EventService.cs b/src/Core/Services/EventService.cs index 179c689c2..45f566f84 100644 --- a/src/Core/Services/EventService.cs +++ b/src/Core/Services/EventService.cs @@ -1,12 +1,12 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Models.Request; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Bit.Core.Services { diff --git a/src/Core/Services/FileUploadService.cs b/src/Core/Services/FileUploadService.cs index 28856ed96..fbc04acd7 100644 --- a/src/Core/Services/FileUploadService.cs +++ b/src/Core/Services/FileUploadService.cs @@ -1,11 +1,12 @@ +using System; using System.Threading.Tasks; using Bit.Core.Abstractions; +using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; -using Bit.Core.Enums; -using System; -namespace Bit.Core.Services { +namespace Bit.Core.Services +{ public class FileUploadService : IFileUploadService { public FileUploadService(ApiService apiService) @@ -41,7 +42,8 @@ namespace Bit.Core.Services { default: throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}"); } - } catch + } + catch { await _apiService.DeleteCipherAttachmentAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId); throw; diff --git a/src/Core/Services/FolderService.cs b/src/Core/Services/FolderService.cs index fdc882476..e2ed35385 100644 --- a/src/Core/Services/FolderService.cs +++ b/src/Core/Services/FolderService.cs @@ -1,15 +1,15 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; using Bit.Core.Models.Response; using Bit.Core.Models.View; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Bit.Core.Services { diff --git a/src/Core/Services/InMemoryStorageService.cs b/src/Core/Services/InMemoryStorageService.cs index fa15fc71b..164565dab 100644 --- a/src/Core/Services/InMemoryStorageService.cs +++ b/src/Core/Services/InMemoryStorageService.cs @@ -1,7 +1,7 @@ -using Bit.Core.Abstractions; -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Newtonsoft.Json; namespace Bit.Core.Services { diff --git a/src/Core/Services/KeyConnectorService.cs b/src/Core/Services/KeyConnectorService.cs index ed8ae5acc..dbeb8cd06 100644 --- a/src/Core/Services/KeyConnectorService.cs +++ b/src/Core/Services/KeyConnectorService.cs @@ -36,7 +36,7 @@ namespace Bit.Core.Services catch (Exception e) { throw new Exception("Unable to reach Key Connector", e); - } + } } public async Task SetUsesKeyConnector(bool usesKeyConnector) diff --git a/src/Core/Services/LiteDbStorageService.cs b/src/Core/Services/LiteDbStorageService.cs index 35a450536..48f1325ba 100644 --- a/src/Core/Services/LiteDbStorageService.cs +++ b/src/Core/Services/LiteDbStorageService.cs @@ -1,9 +1,9 @@ -using Bit.Core.Abstractions; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using LiteDB; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System.Linq; -using System.Threading.Tasks; namespace Bit.Core.Services { diff --git a/src/Core/Services/OrganizationService.cs b/src/Core/Services/OrganizationService.cs index 131a9d03d..8f25c8076 100644 --- a/src/Core/Services/OrganizationService.cs +++ b/src/Core/Services/OrganizationService.cs @@ -1,9 +1,9 @@ -using Bit.Core.Abstractions; -using Bit.Core.Models.Data; -using Bit.Core.Models.Domain; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; namespace Bit.Core.Services { diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index cc776052d..1b35bb5c9 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -1,13 +1,13 @@ -using Bit.Core.Abstractions; -using Bit.Core.Models.Domain; -using Bit.Core.Utilities; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; +using Bit.Core.Models.Domain; +using Bit.Core.Utilities; using Zxcvbn; namespace Bit.Core.Services @@ -384,7 +384,7 @@ namespace Bit.Core.Services { enforcedOptions.Capitalize = true; } - + var includeNumber = GetPolicyBool(currentPolicy, "includeNumber"); if (includeNumber != null && (bool)includeNumber) { @@ -420,7 +420,7 @@ namespace Bit.Core.Services } return null; } - + private string GetPolicyString(Policy policy, string key) { if (policy.Data.ContainsKey(key)) @@ -596,7 +596,7 @@ namespace Bit.Core.Services { options.WordSeparator = options.WordSeparator[0].ToString(); } - + SanitizePasswordLength(options, false); } @@ -683,13 +683,13 @@ namespace Bit.Core.Services if (options.Uppercase.GetValueOrDefault() && options.MinUppercase.GetValueOrDefault() <= 0) { minUppercaseCalc = 1; - } + } else if (!options.Uppercase.GetValueOrDefault()) { minUppercaseCalc = 0; } - if (options.Lowercase.GetValueOrDefault() && options.MinLowercase.GetValueOrDefault() <= 0) + if (options.Lowercase.GetValueOrDefault() && options.MinLowercase.GetValueOrDefault() <= 0) { minLowercaseCalc = 1; } @@ -701,7 +701,7 @@ namespace Bit.Core.Services if (options.Number.GetValueOrDefault() && options.MinNumber.GetValueOrDefault() <= 0) { minNumberCalc = 1; - } + } else if (!options.Number.GetValueOrDefault()) { minNumberCalc = 0; @@ -710,7 +710,7 @@ namespace Bit.Core.Services if (options.Special.GetValueOrDefault() && options.MinSpecial.GetValueOrDefault() <= 0) { minSpecialCalc = 1; - } + } else if (!options.Special.GetValueOrDefault()) { minSpecialCalc = 0; diff --git a/src/Core/Services/PclCryptoFunctionService.cs b/src/Core/Services/PclCryptoFunctionService.cs index 8299f8218..fa8e8693d 100644 --- a/src/Core/Services/PclCryptoFunctionService.cs +++ b/src/Core/Services/PclCryptoFunctionService.cs @@ -1,10 +1,10 @@ -using Bit.Core.Abstractions; -using Bit.Core.Enums; -using PCLCrypto; -using System; +using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using PCLCrypto; using static PCLCrypto.WinRTCrypto; namespace Bit.Core.Services diff --git a/src/Core/Services/PolicyService.cs b/src/Core/Services/PolicyService.cs index e1c2b8c75..d81370870 100644 --- a/src/Core/Services/PolicyService.cs +++ b/src/Core/Services/PolicyService.cs @@ -24,7 +24,7 @@ namespace Bit.Core.Services _stateService = stateService; _organizationService = organizationService; } - + public void ClearCache() { _policyCache = null; diff --git a/src/Core/Services/SearchService.cs b/src/Core/Services/SearchService.cs index 689fbf59a..09610942e 100644 --- a/src/Core/Services/SearchService.cs +++ b/src/Core/Services/SearchService.cs @@ -1,10 +1,10 @@ -using Bit.Core.Abstractions; -using Bit.Core.Models.View; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Models.View; namespace Bit.Core.Services { diff --git a/src/Core/Services/SendService.cs b/src/Core/Services/SendService.cs index 1ca9d4930..b59fad7e6 100644 --- a/src/Core/Services/SendService.cs +++ b/src/Core/Services/SendService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -200,7 +200,8 @@ namespace Bit.Core.Services response = await _apiService.PostSendAsync(request); break; case SendType.File: - try{ + try + { var uploadDataResponse = await _apiService.PostFileTypeSendAsync(request); response = uploadDataResponse.SendResponse; @@ -212,7 +213,8 @@ namespace Bit.Core.Services } catch { - if (response != default){ + if (response != default) + { await _apiService.DeleteSendAsync(response.Id); } throw; @@ -234,7 +236,8 @@ namespace Bit.Core.Services } [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")] - private async Task LegacyServerSendFileUpload(SendRequest request, Send send, EncByteArray encryptedFileData) { + private async Task LegacyServerSendFileUpload(SendRequest request, Send send, EncByteArray encryptedFileData) + { var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}") { { new StringContent(JsonConvert.SerializeObject(request)), "model" }, diff --git a/src/Core/Services/SettingsService.cs b/src/Core/Services/SettingsService.cs index 14984bc72..8fc72cfec 100644 --- a/src/Core/Services/SettingsService.cs +++ b/src/Core/Services/SettingsService.cs @@ -1,7 +1,7 @@ -using Bit.Core.Abstractions; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Newtonsoft.Json.Linq; namespace Bit.Core.Services { diff --git a/src/Core/Services/StateMigrationService.cs b/src/Core/Services/StateMigrationService.cs index 209493d47..5574ad72f 100644 --- a/src/Core/Services/StateMigrationService.cs +++ b/src/Core/Services/StateMigrationService.cs @@ -63,7 +63,7 @@ namespace Bit.Core.Services private class V1Keys { internal const string EnvironmentUrlsKey = "environmentUrls"; - + } private async Task MigrateFrom1To2Async() diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index f26df9457..ea70d64e7 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1,8 +1,8 @@ using System; -using Bit.Core.Abstractions; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; @@ -208,7 +208,7 @@ namespace Bit.Core.Services var key = Constants.BiometricUnlockKey(reconciledOptions.UserId); await SetValueAsync(key, value, reconciledOptions); } - + public async Task GetBiometricLockedAsync(string userId = null) { return (await GetAccountAsync( @@ -1419,7 +1419,7 @@ namespace Bit.Core.Services account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout; account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction; } - + // New account defaults if (account.Settings.VaultTimeout == null) { @@ -1431,7 +1431,7 @@ namespace Bit.Core.Services } await SetThemeAsync(currentTheme, account.Profile.UserId); await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId); - + state.Accounts[account.Profile.UserId] = account; await SaveStateToStorageAsync(state); diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs index a7ce471ab..ba90d1da9 100644 --- a/src/Core/Services/SyncService.cs +++ b/src/Core/Services/SyncService.cs @@ -1,12 +1,12 @@ -using Bit.Core.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Models.Response; using Bit.Core.Utilities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Bit.Core.Services { @@ -378,7 +378,7 @@ namespace Bit.Core.Services private async Task SyncSendsAsync(string userId, List response) { - var sends = response?.ToDictionary(s => s.Id, s => new SendData(s, userId)) ?? + var sends = response?.ToDictionary(s => s.Id, s => new SendData(s, userId)) ?? new Dictionary(); await _sendService.ReplaceAsync(sends); } diff --git a/src/Core/Services/TokenService.cs b/src/Core/Services/TokenService.cs index dcf3ad17e..9edd32b22 100644 --- a/src/Core/Services/TokenService.cs +++ b/src/Core/Services/TokenService.cs @@ -1,11 +1,11 @@ -using Bit.Core.Abstractions; -using Bit.Core.Utilities; -using Newtonsoft.Json.Linq; -using System; +using System; +using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Linq; +using Bit.Core.Abstractions; using Bit.Core.Enums; +using Bit.Core.Utilities; +using Newtonsoft.Json.Linq; namespace Bit.Core.Services { diff --git a/src/Core/Services/TotpService.cs b/src/Core/Services/TotpService.cs index 07c64285a..df74920d7 100644 --- a/src/Core/Services/TotpService.cs +++ b/src/Core/Services/TotpService.cs @@ -1,8 +1,8 @@ -using Bit.Core.Abstractions; +using System; +using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -using System; -using System.Threading.Tasks; namespace Bit.Core.Services { diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index df298ed8d..43adc7866 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -1,7 +1,7 @@ -using Bit.Core.Abstractions; -using System; +using System; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; namespace Bit.Core.Services @@ -165,7 +165,8 @@ namespace Bit.Core.Services userId = await _stateService.GetActiveUserIdAsync(); } - if (await _keyConnectorService.GetUsesKeyConnector()) { + if (await _keyConnectorService.GetUsesKeyConnector()) + { var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId); var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) || isPinProtectedWithKey; @@ -202,10 +203,10 @@ namespace Bit.Core.Services } _lockedCallback?.Invoke(new Tuple(userId, userInitiated)); } - + public async Task LogOutAsync(bool userInitiated = true, string userId = null) { - if(_loggedOutCallback != null) + if (_loggedOutCallback != null) { await _loggedOutCallback.Invoke(new Tuple(userId, userInitiated, false)); } @@ -238,10 +239,12 @@ namespace Bit.Core.Services await _stateService.SetProtectedPinAsync(null, userId); } - public async Task GetVaultTimeout(string userId = null) { + public async Task GetVaultTimeout(string userId = null) + { var vaultTimeout = await _stateService.GetVaultTimeoutAsync(userId); - if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)) { + if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)) + { var policy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout, userId)).First(); // Remove negative values, and ensure it's smaller than maximum allowed value according to policy var policyTimeout = _policyService.GetPolicyInt(policy, "minutes"); @@ -252,12 +255,14 @@ namespace Bit.Core.Services var timeout = vaultTimeout.HasValue ? Math.Min(vaultTimeout.Value, policyTimeout.Value) : policyTimeout.Value; - if (timeout < 0) { + if (timeout < 0) + { timeout = policyTimeout.Value; } // We really shouldn't need to set the value here, but multiple services relies on this value being correct. - if (vaultTimeout != timeout) { + if (vaultTimeout != timeout) + { await _stateService.SetVaultTimeoutAsync(timeout, userId); } diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index ca32ad0a9..f871c54ff 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -1,9 +1,9 @@ -using Bit.Core.Models.Domain; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Bit.Core.Models.Domain; +using Newtonsoft.Json; namespace Bit.Core.Utilities { @@ -194,7 +194,7 @@ namespace Bit.Core.Utilities } return JsonConvert.SerializeObject(obj, jsonSerializationSettings); } - + public static string SerializeJson(object obj, JsonSerializerSettings jsonSerializationSettings) { return JsonConvert.SerializeObject(obj, jsonSerializationSettings); diff --git a/src/Core/Utilities/ExtendedViewModel.cs b/src/Core/Utilities/ExtendedViewModel.cs index fd394ea34..ed586d453 100644 --- a/src/Core/Utilities/ExtendedViewModel.cs +++ b/src/Core/Utilities/ExtendedViewModel.cs @@ -10,7 +10,7 @@ namespace Bit.Core.Utilities public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty(ref T backingStore, T value, Action onChanged = null, - [CallerMemberName]string propertyName = "", string[] additionalPropertyNames = null) + [CallerMemberName] string propertyName = "", string[] additionalPropertyNames = null) { if (EqualityComparer.Default.Equals(backingStore, value)) { diff --git a/src/Core/Utilities/ServiceContainer.cs b/src/Core/Utilities/ServiceContainer.cs index 3d2087087..19450173b 100644 --- a/src/Core/Utilities/ServiceContainer.cs +++ b/src/Core/Utilities/ServiceContainer.cs @@ -1,8 +1,8 @@ -using Bit.Core.Abstractions; -using Bit.Core.Services; -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Abstractions; +using Bit.Core.Services; namespace Bit.Core.Utilities { @@ -11,7 +11,7 @@ namespace Bit.Core.Utilities public static Dictionary RegisteredServices { get; set; } = new Dictionary(); public static bool Inited { get; set; } - public static void Init(string customUserAgent = null, string clearCipherCacheKey = null, + public static void Init(string customUserAgent = null, string clearCipherCacheKey = null, string[] allClearCipherCacheKeys = null) { if (Inited) @@ -39,8 +39,8 @@ namespace Bit.Core.Utilities var organizationService = new OrganizationService(stateService); var settingsService = new SettingsService(stateService); var fileUploadService = new FileUploadService(apiService); - var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService, - fileUploadService, storageService, i18nService, () => searchService, clearCipherCacheKey, + var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService, + fileUploadService, storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys); var folderService = new FolderService(cryptoService, stateService, apiService, i18nService, cipherService); var collectionService = new CollectionService(cryptoService, stateService, i18nService); @@ -52,18 +52,18 @@ namespace Bit.Core.Utilities organizationService); var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService, folderService, cipherService, collectionService, searchService, messagingService, tokenService, - policyService, keyConnectorService, + policyService, keyConnectorService, (extras) => { messagingService.Send("locked", extras); return Task.CompletedTask; - }, + }, (extras) => { messagingService.Send("logout", extras); return Task.CompletedTask; }); - var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService, + var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService, cryptoService, collectionService, organizationService, messagingService, policyService, sendService, keyConnectorService, (extras) => { @@ -73,14 +73,14 @@ namespace Bit.Core.Utilities var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService); var totpService = new TotpService(stateService, cryptoFunctionService); - var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, - tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, + var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, + tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, keyConnectorService); var exportService = new ExportService(folderService, cipherService, cryptoService); var auditService = new AuditService(cryptoFunctionService, apiService); var environmentService = new EnvironmentService(apiService, stateService); var eventService = new EventService(apiService, stateService, organizationService, cipherService); - var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, + var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService, cryptoService); Register("tokenService", tokenService); diff --git a/store/google/Publisher/Program.cs b/store/google/Publisher/Program.cs index 68cec9356..bbcb479bc 100644 --- a/store/google/Publisher/Program.cs +++ b/store/google/Publisher/Program.cs @@ -1,11 +1,11 @@ -using Google.Apis.AndroidPublisher.v3; -using Google.Apis.AndroidPublisher.v3.Data; -using Google.Apis.Auth.OAuth2; -using Google.Apis.Services; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Google.Apis.AndroidPublisher.v3; +using Google.Apis.AndroidPublisher.v3.Data; +using Google.Apis.Auth.OAuth2; +using Google.Apis.Services; namespace Bit.Publisher { diff --git a/test/Common/AutoFixture/Attributes/AutoSubDataAttribute.cs b/test/Common/AutoFixture/Attributes/AutoSubDataAttribute.cs index fc4db5fe9..3e01d420e 100644 --- a/test/Common/AutoFixture/Attributes/AutoSubDataAttribute.cs +++ b/test/Common/AutoFixture/Attributes/AutoSubDataAttribute.cs @@ -1,4 +1,4 @@ -using AutoFixture.AutoNSubstitute; +using AutoFixture.AutoNSubstitute; namespace Bit.Test.Common.AutoFixture.Attributes { diff --git a/test/Common/AutoFixture/Attributes/CustomAutoDataAttribute.cs b/test/Common/AutoFixture/Attributes/CustomAutoDataAttribute.cs index cd2feebca..d6c5863b1 100644 --- a/test/Common/AutoFixture/Attributes/CustomAutoDataAttribute.cs +++ b/test/Common/AutoFixture/Attributes/CustomAutoDataAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using AutoFixture; using AutoFixture.Xunit2; diff --git a/test/Common/AutoFixture/Attributes/InlineAutoSubDataAttribute.cs b/test/Common/AutoFixture/Attributes/InlineAutoSubDataAttribute.cs index feccc4e76..b74f1ba02 100644 --- a/test/Common/AutoFixture/Attributes/InlineAutoSubDataAttribute.cs +++ b/test/Common/AutoFixture/Attributes/InlineAutoSubDataAttribute.cs @@ -1,4 +1,4 @@ -using AutoFixture.AutoNSubstitute; +using AutoFixture.AutoNSubstitute; namespace Bit.Test.Common.AutoFixture.Attributes { diff --git a/test/Common/AutoFixture/Attributes/InlineCustomAutoDataAttribute.cs b/test/Common/AutoFixture/Attributes/InlineCustomAutoDataAttribute.cs index d36f963a4..9a9a6730a 100644 --- a/test/Common/AutoFixture/Attributes/InlineCustomAutoDataAttribute.cs +++ b/test/Common/AutoFixture/Attributes/InlineCustomAutoDataAttribute.cs @@ -1,8 +1,8 @@ -using System; +using System; +using AutoFixture; +using AutoFixture.Xunit2; using Xunit; using Xunit.Sdk; -using AutoFixture.Xunit2; -using AutoFixture; namespace Bit.Test.Common.AutoFixture.Attributes { diff --git a/test/Common/AutoFixture/Attributes/InlineSutAutoDataAttribute.cs b/test/Common/AutoFixture/Attributes/InlineSutAutoDataAttribute.cs index 89eebad8c..cc04e062a 100644 --- a/test/Common/AutoFixture/Attributes/InlineSutAutoDataAttribute.cs +++ b/test/Common/AutoFixture/Attributes/InlineSutAutoDataAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using AutoFixture; diff --git a/test/Common/AutoFixture/Attributes/SutAutoDataAttribute.cs b/test/Common/AutoFixture/Attributes/SutAutoDataAttribute.cs index e7c06a88f..2a9545110 100644 --- a/test/Common/AutoFixture/Attributes/SutAutoDataAttribute.cs +++ b/test/Common/AutoFixture/Attributes/SutAutoDataAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; namespace Bit.Test.Common.AutoFixture.Attributes diff --git a/test/Common/AutoFixture/FixtureExtensions.cs b/test/Common/AutoFixture/FixtureExtensions.cs index a23eb7d0c..764a38c81 100644 --- a/test/Common/AutoFixture/FixtureExtensions.cs +++ b/test/Common/AutoFixture/FixtureExtensions.cs @@ -1,4 +1,4 @@ -using AutoFixture; +using AutoFixture; using AutoFixture.AutoNSubstitute; namespace Bit.Test.Common.AutoFixture diff --git a/test/Common/AutoFixture/ISutProvider.cs b/test/Common/AutoFixture/ISutProvider.cs index c72dc4a27..cbd8c6e6f 100644 --- a/test/Common/AutoFixture/ISutProvider.cs +++ b/test/Common/AutoFixture/ISutProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Bit.Test.Common.AutoFixture { diff --git a/test/Common/AutoFixture/SutProvider.cs b/test/Common/AutoFixture/SutProvider.cs index e3149afc6..989bc590d 100644 --- a/test/Common/AutoFixture/SutProvider.cs +++ b/test/Common/AutoFixture/SutProvider.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using AutoFixture; using AutoFixture.Kernel; -using System.Reflection; -using System.Linq; namespace Bit.Test.Common.AutoFixture { diff --git a/test/Common/AutoFixture/SutProviderCustomization.cs b/test/Common/AutoFixture/SutProviderCustomization.cs index b5bc84d1d..c323ed4c5 100644 --- a/test/Common/AutoFixture/SutProviderCustomization.cs +++ b/test/Common/AutoFixture/SutProviderCustomization.cs @@ -1,4 +1,4 @@ -using System; +using System; using AutoFixture; using AutoFixture.Kernel; diff --git a/test/Common/TestHelper.cs b/test/Common/TestHelper.cs index 7ce60f325..c70373591 100644 --- a/test/Common/TestHelper.cs +++ b/test/Common/TestHelper.cs @@ -1,9 +1,9 @@ -using System.Reflection; +using System; using System.IO; using System.Linq; -using Xunit; -using System; +using System.Reflection; using Newtonsoft.Json; +using Xunit; namespace Bit.Test.Common { diff --git a/test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs b/test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs index 8f745ebea..6d4f47aad 100644 --- a/test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs +++ b/test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs @@ -1,4 +1,4 @@ -using System; +using System; using AutoFixture; using Bit.Core.Models.Domain; using Bit.Test.Common.AutoFixture; diff --git a/test/Core.Test/AutoFixture/Domain/SymmetricCryptoKeyCustomization.cs b/test/Core.Test/AutoFixture/Domain/SymmetricCryptoKeyCustomization.cs index 41355b86d..742171a1f 100644 --- a/test/Core.Test/AutoFixture/Domain/SymmetricCryptoKeyCustomization.cs +++ b/test/Core.Test/AutoFixture/Domain/SymmetricCryptoKeyCustomization.cs @@ -1,4 +1,4 @@ -using System; +using System; using AutoFixture; using Bit.Core.Models.Domain; using Bit.Core.Services; diff --git a/test/Core.Test/AutoFixture/Send/SendCustomizations.cs b/test/Core.Test/AutoFixture/Send/SendCustomizations.cs index 617ba8c8e..a18f9707a 100644 --- a/test/Core.Test/AutoFixture/Send/SendCustomizations.cs +++ b/test/Core.Test/AutoFixture/Send/SendCustomizations.cs @@ -1,11 +1,11 @@ -using AutoFixture; +using AutoFixture; +using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; using Bit.Core.Models.Response; using Bit.Core.Models.View; -using Bit.Core.Enums; namespace Bit.Core.Test.AutoFixture { diff --git a/test/Core.Test/Models/Data/SendDataTests.cs b/test/Core.Test/Models/Data/SendDataTests.cs index 06bb54b12..e456a6e00 100644 --- a/test/Core.Test/Models/Data/SendDataTests.cs +++ b/test/Core.Test/Models/Data/SendDataTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.Models.Data; +using Bit.Core.Models.Data; using Bit.Core.Models.Response; using Bit.Core.Test.AutoFixture; using Bit.Test.Common; diff --git a/test/Core.Test/Models/Domain/SendTests.cs b/test/Core.Test/Models/Domain/SendTests.cs index ff9883829..b5d58c1e1 100644 --- a/test/Core.Test/Models/Domain/SendTests.cs +++ b/test/Core.Test/Models/Domain/SendTests.cs @@ -1,17 +1,17 @@ -using System; +using System; using System.Linq; -using Bit.Core.Models.Data; -using Bit.Core.Models.Domain; -using Bit.Test.Common; using System.Text; +using AutoFixture.AutoNSubstitute; using Bit.Core.Abstractions; using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; +using Bit.Core.Test.AutoFixture; using Bit.Core.Utilities; +using Bit.Test.Common; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -using Bit.Core.Test.AutoFixture; -using AutoFixture.AutoNSubstitute; namespace Bit.Core.Test.Models.Domain { diff --git a/test/Core.Test/Models/Request/SendRequestTests.cs b/test/Core.Test/Models/Request/SendRequestTests.cs index f2fa4d9bb..028dce25a 100644 --- a/test/Core.Test/Models/Request/SendRequestTests.cs +++ b/test/Core.Test/Models/Request/SendRequestTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; diff --git a/test/Core.Test/Services/CipherServiceTests.cs b/test/Core.Test/Services/CipherServiceTests.cs index cecda9bd9..494fa192c 100644 --- a/test/Core.Test/Services/CipherServiceTests.cs +++ b/test/Core.Test/Services/CipherServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -52,7 +52,7 @@ namespace Bit.Core.Test.Services .Returns(data); sutProvider.GetDependency().MakeEncKeyAsync(Arg.Any()).Returns(new Tuple(null, encKey)); sutProvider.GetDependency().PostCipherAttachmentAsync(cipher.Id, Arg.Any()) - .Throws(new ApiException(new ErrorResponse {StatusCode = statusCode})); + .Throws(new ApiException(new ErrorResponse { StatusCode = statusCode })); sutProvider.GetDependency().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any()) .Returns(response); @@ -76,7 +76,7 @@ namespace Bit.Core.Test.Services sutProvider.GetDependency().PostCipherAttachmentAsync(cipher.Id, Arg.Any()) .Throws(expectedException); - var actualException = await Assert.ThrowsAsync(async () => + var actualException = await Assert.ThrowsAsync(async () => await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer)); Assert.Equal(expectedException.Error.StatusCode, actualException.Error.StatusCode); diff --git a/test/Core.Test/Services/CryptoFunctionServiceTests.cs b/test/Core.Test/Services/CryptoFunctionServiceTests.cs index b95335e17..eea1af98a 100644 --- a/test/Core.Test/Services/CryptoFunctionServiceTests.cs +++ b/test/Core.Test/Services/CryptoFunctionServiceTests.cs @@ -1,11 +1,11 @@ - + using System; -using System.Threading.Tasks; -using Xunit; -using Bit.Core.Services; -using Bit.Core.Enums; -using Bit.Test.Common.AutoFixture.Attributes; using System.Text; +using System.Threading.Tasks; +using Bit.Core.Enums; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; namespace Bit.Core.Test.Services { diff --git a/test/Core.Test/Services/SendServiceTests.cs b/test/Core.Test/Services/SendServiceTests.cs index badf2c742..6038465d5 100644 --- a/test/Core.Test/Services/SendServiceTests.cs +++ b/test/Core.Test/Services/SendServiceTests.cs @@ -1,28 +1,28 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Net.Http; +using System.Text; using System.Threading.Tasks; using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; +using Bit.Core.Models.Request; using Bit.Core.Models.Response; +using Bit.Core.Models.View; using Bit.Core.Services; +using Bit.Core.Test.AutoFixture; using Bit.Core.Utilities; -using Bit.Core.Enums; using Bit.Test.Common; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Newtonsoft.Json; using NSubstitute; -using Xunit; -using System.Text; -using System.Net.Http; -using Bit.Core.Models.Request; -using Bit.Core.Test.AutoFixture; -using System.Linq.Expressions; -using Bit.Core.Models.View; -using Bit.Core.Exceptions; using NSubstitute.ExceptionExtensions; +using Xunit; namespace Bit.Core.Test.Services { @@ -143,7 +143,7 @@ namespace Bit.Core.Test.Services { // TODO restore this once race condition is fixed or GHA can re-run jobs on individual platforms return; - + var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); sutProvider.GetDependency().HasKeyAsync().Returns(true); ServiceContainer.Register("cryptoService", sutProvider.GetDependency()); diff --git a/test/Playground/Program.cs b/test/Playground/Program.cs index ca57eb5d2..c8e4708a7 100644 --- a/test/Playground/Program.cs +++ b/test/Playground/Program.cs @@ -1,6 +1,6 @@ -using Bit.Core.Services; -using System; +using System; using System.Threading.Tasks; +using Bit.Core.Services; namespace Bit.Playground { From c988175e502cbd0ae4615eb89a9fa96389dcc844 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 26 Apr 2022 17:27:13 +0200 Subject: [PATCH 078/100] [TI-8] Add .git-blame-ignore-revs (#1891) --- .git-blame-ignore-revs | 2 ++ .github/workflows/build.yml | 6 +++--- README.md | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..e6dc4e220 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# .NET format https://github.com/bitwarden/mobile/pull/1738 +04539af2a66668b6e85476d5cf318c9150ec4357 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f9ae9888..0ce5e0174 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,9 +109,9 @@ jobs: run: dotnet tool restore shell: pwsh - # - name: Verify Format - # run: dotnet tool run dotnet-format --check - # shell: pwsh + - name: Verify Format + run: dotnet tool run dotnet-format --check + shell: pwsh - name: Run Core tests run: dotnet test test/Core.Test/Core.Test.csproj diff --git a/README.md b/README.md index 32be97200..cee1c5d5e 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,23 @@ Code contributions are welcome! Visual Studio with Xamarin is required to work o Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file. Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file. + +### Dotnet-format + +We recently migrated to using dotnet-format as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps: + +1. Check out your local Branch +2. Run `git merge e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35` +3. Resolve any merge conflicts, commit. +4. Run `dotnet tool run dotnet-format` +5. Commit +6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357` +7. Push + +#### Git blame + +We also recommend that you configure git to ignore the prettier revision using: + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` From 769851adfe1c299a668781a86ffb0d1cc55e17b6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 26 Apr 2022 22:51:58 +0200 Subject: [PATCH 079/100] Update .gitattributes to fix build issue (#1892) --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 1ff0c4230..6f3fcbddb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### -* text=auto +* text=auto eol=lf ############################################################################### # Set default behavior for command prompt diff. From cdc41d3bef171665892744956738a5c24a4111eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 08:24:29 -0700 Subject: [PATCH 080/100] Bumped version to 2.18.0 (#1893) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Android/Properties/AndroidManifest.xml | 2 +- src/iOS.Autofill/Info.plist | 2 +- src/iOS.Extension/Info.plist | 2 +- src/iOS.ShareExtension/Info.plist | 2 +- src/iOS/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index 5ae9ff0c4..1a01d61ca 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index 1eb983d45..ca9c578a9 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2.17.1 + 2.18.0 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index b192183aa..54bf21986 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2.17.1 + 2.18.0 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index a9d128948..0942ff423 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2.17.1 + 2.18.0 CFBundleVersion 1 MinimumOSVersion diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index 5baae1815..b1b084588 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2.17.1 + 2.18.0 CFBundleVersion 1 CFBundleIconName From 1bbe8d8b985d2aefc4b0e91eaab96f69f70dbfc3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 09:04:29 -0700 Subject: [PATCH 081/100] Bumped version to 2.18.1 (#1894) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Android/Properties/AndroidManifest.xml | 2 +- src/iOS.Autofill/Info.plist | 2 +- src/iOS.Extension/Info.plist | 2 +- src/iOS.ShareExtension/Info.plist | 2 +- src/iOS/Info.plist | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index 1a01d61ca..dfb170dee 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/iOS.Autofill/Info.plist b/src/iOS.Autofill/Info.plist index ca9c578a9..0f732194f 100644 --- a/src/iOS.Autofill/Info.plist +++ b/src/iOS.Autofill/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.autofill CFBundleShortVersionString - 2.18.0 + 2.18.1 CFBundleVersion 1 CFBundleLocalizations diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist index 54bf21986..9d4bd5c44 100644 --- a/src/iOS.Extension/Info.plist +++ b/src/iOS.Extension/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden.find-login-action-extension CFBundleShortVersionString - 2.18.0 + 2.18.1 CFBundleLocalizations en diff --git a/src/iOS.ShareExtension/Info.plist b/src/iOS.ShareExtension/Info.plist index 0942ff423..ba8651ee9 100644 --- a/src/iOS.ShareExtension/Info.plist +++ b/src/iOS.ShareExtension/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 2.18.0 + 2.18.1 CFBundleVersion 1 MinimumOSVersion diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index b1b084588..cff45967f 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -11,7 +11,7 @@ CFBundleIdentifier com.8bit.bitwarden CFBundleShortVersionString - 2.18.0 + 2.18.1 CFBundleVersion 1 CFBundleIconName From c251b950d1212436de43ff18149f04652ee8db74 Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Thu, 28 Apr 2022 10:51:13 -0300 Subject: [PATCH 082/100] PS-77 Updated two-factor email request to include the device identifier to check whether to send the 2fa email because of a new device (#1895) --- src/App/Pages/Accounts/TwoFactorPageViewModel.cs | 5 ++++- src/Core/Models/Request/TwoFactorEmailRequest.cs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs index 15de50f51..7de82e1f8 100644 --- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs @@ -29,6 +29,7 @@ namespace Bit.App.Pages private readonly IBroadcasterService _broadcasterService; private readonly IStateService _stateService; private readonly II18nService _i18nService; + private readonly IAppIdService _appIdService; private TwoFactorProviderType? _selectedProviderType; private string _totpInstruction; @@ -49,6 +50,7 @@ namespace Bit.App.Pages _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _stateService = ServiceContainer.Resolve("stateService"); _i18nService = ServiceContainer.Resolve("i18nService"); + _appIdService = ServiceContainer.Resolve("appIdService"); PageTitle = AppResources.TwoStepLogin; SubmitCommand = new Command(async () => await SubmitAsync()); @@ -380,7 +382,8 @@ namespace Bit.App.Pages var request = new TwoFactorEmailRequest { Email = _authService.Email, - MasterPasswordHash = _authService.MasterPasswordHash + MasterPasswordHash = _authService.MasterPasswordHash, + DeviceIdentifier = await _appIdService.GetAppIdAsync() }; await _apiService.PostTwoFactorEmailAsync(request); if (showLoading) diff --git a/src/Core/Models/Request/TwoFactorEmailRequest.cs b/src/Core/Models/Request/TwoFactorEmailRequest.cs index 3446df586..2b5784099 100644 --- a/src/Core/Models/Request/TwoFactorEmailRequest.cs +++ b/src/Core/Models/Request/TwoFactorEmailRequest.cs @@ -4,5 +4,6 @@ { public string Email { get; set; } public string MasterPasswordHash { get; set; } + public string DeviceIdentifier { get; set; } } } From b081a8c6347516e7ebe9577f4e40caf3421fe0c6 Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Thu, 28 Apr 2022 15:30:33 -0400 Subject: [PATCH 083/100] fix a11y issue with hCaptcha on iOS (#1896) --- src/App/Pages/CaptchaProtectedViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/App/Pages/CaptchaProtectedViewModel.cs b/src/App/Pages/CaptchaProtectedViewModel.cs index d31d338ae..042d1423c 100644 --- a/src/App/Pages/CaptchaProtectedViewModel.cs +++ b/src/App/Pages/CaptchaProtectedViewModel.cs @@ -37,11 +37,14 @@ namespace Bit.App.Pages bool cancelled = false; try { + // PrefersEphemeralWebBrowserSession should be false to allow access to the hCaptcha accessibility + // cookie set in the default browser + // https://www.hcaptcha.com/accessibility var options = new WebAuthenticatorOptions { Url = new Uri(url), CallbackUrl = new Uri(callbackUri), - PrefersEphemeralWebBrowserSession = true, + PrefersEphemeralWebBrowserSession = false, }; authResult = await WebAuthenticator.AuthenticateAsync(options); } From 604e3b68922148c166fb890eebc7e6a062116d3e Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 11 May 2022 07:02:40 +1000 Subject: [PATCH 084/100] Remove testing requirements from pr template (#1901) --- .github/PULL_REQUEST_TEMPLATE.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 807709ec5..2ba0505a3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,11 +21,6 @@ -## Testing requirements - - - - ## Before you submit - [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required) - [ ] This change requires a **documentation update** (notify the documentation team) From bcbc2738ca43d73bd5fe53aaaaa5708cf6f5946b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Wed, 18 May 2022 14:58:49 +0100 Subject: [PATCH 085/100] PS-591 Fix avoid ambiguous characters #1664 (#1906) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PS-591 - iOS - Avoid ambiguous characters is activated inside the main client, but is deactivated when creating a vault item from the autofill prompt. #1664 - Refactor the name of the property Ambiguous to AvoidAmbiguous, this naming was misleading. - Fixed bug where the boolean value for the AvoidAmbiguous property was being stored inverted. * PS-591 - iOS - Avoid ambiguous characters is activated inside the main client, but is deactivated when creating a vault item from the autofill prompt. #1664 - Changed AvoidAmbiguous to AllowAmbiguous - Added wrapper Prop to bind UI Toggle to VM Co-authored-by: André Bispo --- src/App/Pages/Generator/GeneratorPage.xaml | 2 +- .../Pages/Generator/GeneratorPageViewModel.cs | 24 +++++++++++++------ .../Domain/PasswordGenerationOptions.cs | 6 ++--- .../Services/PasswordGenerationService.cs | 6 ++--- .../PasswordGeneratorViewController.cs | 4 ++-- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml index 66a04a27a..2c581b8f5 100644 --- a/src/App/Pages/Generator/GeneratorPage.xaml +++ b/src/App/Pages/Generator/GeneratorPage.xaml @@ -277,7 +277,7 @@ StyleClass="box-label-regular" HorizontalOptions="StartAndExpand" />
diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs index 1b694a83c..2332267ed 100644 --- a/src/App/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs @@ -23,7 +23,7 @@ namespace Bit.App.Pages private bool _lowercase; private bool _number; private bool _special; - private bool _avoidAmbiguous; + private bool _allowAmbiguousChars; private int _minNumber; private int _minSpecial; private int _length = 5; @@ -130,19 +130,29 @@ namespace Bit.App.Pages } } - public bool AvoidAmbiguous + public bool AllowAmbiguousChars { - get => _avoidAmbiguous; + get => _allowAmbiguousChars; set { - if (SetProperty(ref _avoidAmbiguous, value)) + if (SetProperty(ref _allowAmbiguousChars, value, + additionalPropertyNames: new string[] + { + nameof(AvoidAmbiguousChars) + })) { - _options.Ambiguous = !value; + _options.AllowAmbiguousChar = value; var task = SaveOptionsAsync(); } } } + public bool AvoidAmbiguousChars + { + get => !AllowAmbiguousChars; + set => AllowAmbiguousChars = !value; + } + public int MinNumber { get => _minNumber; @@ -315,7 +325,7 @@ namespace Bit.App.Pages private void LoadFromOptions() { - AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault(); + AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault(); TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0; IsPassword = TypeSelectedIndex == 0; MinNumber = _options.MinNumber.GetValueOrDefault(); @@ -333,7 +343,7 @@ namespace Bit.App.Pages private void SetOptions() { - _options.Ambiguous = !AvoidAmbiguous; + _options.AllowAmbiguousChar = AllowAmbiguousChars; _options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password"; _options.MinNumber = MinNumber; _options.MinSpecial = MinSpecial; diff --git a/src/Core/Models/Domain/PasswordGenerationOptions.cs b/src/Core/Models/Domain/PasswordGenerationOptions.cs index 9f0717c2b..16afec6bf 100644 --- a/src/Core/Models/Domain/PasswordGenerationOptions.cs +++ b/src/Core/Models/Domain/PasswordGenerationOptions.cs @@ -9,7 +9,7 @@ if (defaultOptions) { Length = 14; - Ambiguous = false; + AllowAmbiguousChar = true; Number = true; MinNumber = 1; Uppercase = true; @@ -27,7 +27,7 @@ } public int? Length { get; set; } - public bool? Ambiguous { get; set; } + public bool? AllowAmbiguousChar { get; set; } public bool? Number { get; set; } public int? MinNumber { get; set; } public bool? Uppercase { get; set; } @@ -45,7 +45,7 @@ public void Merge(PasswordGenerationOptions defaults) { Length = Length ?? defaults.Length; - Ambiguous = Ambiguous ?? defaults.Ambiguous; + AllowAmbiguousChar = AllowAmbiguousChar ?? defaults.AllowAmbiguousChar; Number = Number ?? defaults.Number; MinNumber = MinNumber ?? defaults.MinNumber; Uppercase = Uppercase ?? defaults.Uppercase; diff --git a/src/Core/Services/PasswordGenerationService.cs b/src/Core/Services/PasswordGenerationService.cs index 1b35bb5c9..b82152e97 100644 --- a/src/Core/Services/PasswordGenerationService.cs +++ b/src/Core/Services/PasswordGenerationService.cs @@ -93,7 +93,7 @@ namespace Bit.Core.Services // Build out other character sets var allCharSet = string.Empty; var lowercaseCharSet = LowercaseCharSet; - if (options.Ambiguous.GetValueOrDefault()) + if (options.AllowAmbiguousChar.GetValueOrDefault()) { lowercaseCharSet = string.Concat(lowercaseCharSet, "l"); } @@ -103,7 +103,7 @@ namespace Bit.Core.Services } var uppercaseCharSet = UppercaseCharSet; - if (options.Ambiguous.GetValueOrDefault()) + if (options.AllowAmbiguousChar.GetValueOrDefault()) { uppercaseCharSet = string.Concat(uppercaseCharSet, "IO"); } @@ -113,7 +113,7 @@ namespace Bit.Core.Services } var numberCharSet = NumberCharSet; - if (options.Ambiguous.GetValueOrDefault()) + if (options.AllowAmbiguousChar.GetValueOrDefault()) { numberCharSet = string.Concat(numberCharSet, "01"); } diff --git a/src/iOS.Core/Controllers/PasswordGeneratorViewController.cs b/src/iOS.Core/Controllers/PasswordGeneratorViewController.cs index ba235edfd..17bf8a911 100644 --- a/src/iOS.Core/Controllers/PasswordGeneratorViewController.cs +++ b/src/iOS.Core/Controllers/PasswordGeneratorViewController.cs @@ -101,7 +101,7 @@ namespace Bit.iOS.Core.Controllers MinNumbersCell.Value = options.MinNumber.GetValueOrDefault(1); MinSpecialCell.Value = options.MinSpecial.GetValueOrDefault(1); LengthCell.Value = options.Length.GetValueOrDefault(14); - AmbiguousCell.Switch.On = options.Ambiguous.GetValueOrDefault(); + AmbiguousCell.Switch.On = !options.AllowAmbiguousChar.GetValueOrDefault(); NumWordsCell.Value = options.NumWords.GetValueOrDefault(3); WordSeparatorCell.TextField.Text = options.WordSeparator ?? ""; @@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers Special = SpecialCell.Switch.On, MinSpecial = MinSpecialCell.Value, MinNumber = MinNumbersCell.Value, - Ambiguous = AmbiguousCell.Switch.On, + AllowAmbiguousChar = !AmbiguousCell.Switch.On, NumWords = NumWordsCell.Value, WordSeparator = WordSeparatorCell.TextField.Text, Capitalize = CapitalizeCell.Switch.On, From 22c746543a2f41e38e076bdd4c8623c3bae6ad15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Wed, 18 May 2022 17:59:19 +0100 Subject: [PATCH 086/100] PS-518 - Add setting to block AppCenter / Analytics - Mobile (#1905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PS-518 - Add setting to block AppCenter / Analytics - Mobile - Added another entry into Settings page under the Others section - Added prompt to ask user to enable / disable Crash Reports - Added compilation tags to remove if the build is FDroid * PS-518 Add setting to block AppCenter / Analytics - Mobile - Reduced FDroid compilation tags throughout the code - Added Init, Enable and State methods to Logger - Simplified SettingsPageViewModel Enable/Disable code * PS-518 Add setting to block AppCenter / Analytics - Mobile - Appcenter references were removed from App project, - Removed FDroid build.yml code that was deleting Appcenter packages from App.csproj Co-authored-by: André Bispo --- .github/workflows/build.yml | 12 ---- src/Android/Android.csproj | 1 - src/Android/MainActivity.cs | 6 +- src/Android/Services/BiometricService.cs | 13 ++-- src/Android/Utilities/AppCenterHelper.cs | 58 ----------------- src/App/App.csproj | 1 - .../SettingsPage/SettingsPage.xaml.cs | 4 ++ .../SettingsPage/SettingsPageViewModel.cs | 40 +++++++++++- src/App/Resources/AppResources.Designer.cs | 12 ++++ src/App/Resources/AppResources.resx | 6 ++ src/Core/Abstractions/ILogger.cs | 18 +++++ src/Core/Services/Logging/DebugLogger.cs | 7 ++ src/Core/Services/Logging/Logger.cs | 65 +++++++++++++++++++ src/Core/Services/Logging/LoggerHelper.cs | 6 +- src/Core/Services/Logging/StubLogger.cs | 7 ++ .../CredentialProviderViewController.cs | 7 +- src/iOS.Core/Utilities/AppCenterHelper.cs | 56 ---------------- src/iOS.Core/Utilities/iOSCoreHelpers.cs | 9 +-- src/iOS.Core/iOS.Core.csproj | 1 - src/iOS.Extension/LoadingViewController.cs | 7 +- .../LoadingViewController.cs | 11 +--- src/iOS/AppDelegate.cs | 2 +- 22 files changed, 172 insertions(+), 177 deletions(-) delete mode 100644 src/Android/Utilities/AppCenterHelper.cs delete mode 100644 src/iOS.Core/Utilities/AppCenterHelper.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ce5e0174..104fa1176 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -304,18 +304,6 @@ jobs: $xml.Save($androidPath); - Write-Output "########################################" - Write-Output "##### Uninstall from App.csproj" - Write-Output "########################################" - - $xml=New-Object XML; - $xml.Load($appPath); - - $appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']"); - $appCenterNode.ParentNode.RemoveChild($appCenterNode); - - $xml.Save($appPath); - Write-Output "########################################" Write-Output "##### Uninstall from Core.csproj" Write-Output "########################################" diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index f04d1acf0..f03f2776e 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -145,7 +145,6 @@ - diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index fdb12d364..af98b744c 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -69,10 +69,7 @@ namespace Bit.Droid Window.AddFlags(Android.Views.WindowManagerFlags.Secure); } -#if !DEBUG && !FDROID - var appCenterHelper = new AppCenterHelper(_appIdService, _stateService); - var appCenterTask = appCenterHelper.InitAsync(); -#endif + ServiceContainer.Resolve("logger").InitAsync(); var toplayout = Window?.DecorView?.RootView; if (toplayout != null) @@ -85,6 +82,7 @@ namespace Bit.Droid _appOptions = GetOptions(); LoadApplication(new App.App(_appOptions)); + _broadcasterService.Subscribe(_activityKey, (message) => { if (message.Command == "startEventTimer") diff --git a/src/Android/Services/BiometricService.cs b/src/Android/Services/BiometricService.cs index ee717eb14..e14ae24ed 100644 --- a/src/Android/Services/BiometricService.cs +++ b/src/Android/Services/BiometricService.cs @@ -3,11 +3,10 @@ using System.Threading.Tasks; using Android.OS; using Android.Security.Keystore; using Bit.Core.Abstractions; +using Bit.Core.Services; +using Bit.Core.Utilities; using Java.Security; using Javax.Crypto; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif namespace Bit.Droid.Services { @@ -74,9 +73,7 @@ namespace Bit.Droid.Services catch (InvalidKeyException e) { // Fallback for old bitwarden users without a key -#if !FDROID - Crashes.TrackError(e); -#endif + LoggerHelper.LogEvenIfCantBeResolved(e); CreateKey(); } @@ -101,9 +98,7 @@ namespace Bit.Droid.Services { // Catch silently to allow biometrics to function on devices that are in a state where key generation // is not functioning -#if !FDROID - Crashes.TrackError(e); -#endif + LoggerHelper.LogEvenIfCantBeResolved(e); } } } diff --git a/src/Android/Utilities/AppCenterHelper.cs b/src/Android/Utilities/AppCenterHelper.cs deleted file mode 100644 index aa3313a3e..000000000 --- a/src/Android/Utilities/AppCenterHelper.cs +++ /dev/null @@ -1,58 +0,0 @@ -#if !FDROID -using Bit.Core.Abstractions; -using System.Threading.Tasks; -using Microsoft.AppCenter; -using Microsoft.AppCenter.Crashes; -using Newtonsoft.Json; - -namespace Bit.Droid.Utilities -{ - public class AppCenterHelper - { - private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42"; - - private readonly IAppIdService _appIdService; - private readonly IStateService _stateService; - - private string _userId; - private string _appId; - - public AppCenterHelper( - IAppIdService appIdService, - IStateService stateService) - { - _appIdService = appIdService; - _stateService = stateService; - } - - public async Task InitAsync() - { - _userId = await _stateService.GetActiveUserIdAsync(); - _appId = await _appIdService.GetAppIdAsync(); - - AppCenter.Start(AppSecret, typeof(Crashes)); - AppCenter.SetUserId(_userId); - - Crashes.GetErrorAttachments = (ErrorReport report) => - { - return new ErrorAttachmentLog[] - { - ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"), - }; - }; - } - - public string Description - { - get - { - return JsonConvert.SerializeObject(new - { - AppId = _appId, - UserId = _userId - }, Formatting.Indented); - } - } - } -} -#endif diff --git a/src/App/App.csproj b/src/App/App.csproj index 60e989699..2293da7e1 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -13,7 +13,6 @@ - diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs index 8a798c11c..7d1b1534d 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml.cs @@ -167,6 +167,10 @@ namespace Bit.App.Pages { await _vm.UpdatePinAsync(); } + else if (item.Name == AppResources.ReportCrashLogs) + { + await _vm.LoggerReportingAsync(); + } else { var biometricName = AppResources.Biometrics; diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 06727300f..a6200f465 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -7,10 +7,10 @@ using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; +using Bit.Core.Services; using Bit.Core.Utilities; using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; -using ZXing.Client.Result; namespace Bit.App.Pages { @@ -29,7 +29,7 @@ namespace Bit.App.Pages private readonly ILocalizeService _localizeService; private readonly IKeyConnectorService _keyConnectorService; private readonly IClipboardService _clipboardService; - + private readonly ILogger _loggerService; private const int CustomVaultTimeoutValue = -100; private bool _supportsBiometric; @@ -39,6 +39,7 @@ namespace Bit.App.Pages private string _vaultTimeoutDisplayValue; private string _vaultTimeoutActionDisplayValue; private bool _showChangeMasterPassword; + private bool _reportLoggingEnabled; private List> _vaultTimeouts = new List> @@ -79,6 +80,7 @@ namespace Bit.App.Pages _localizeService = ServiceContainer.Resolve("localizeService"); _keyConnectorService = ServiceContainer.Resolve("keyConnectorService"); _clipboardService = ServiceContainer.Resolve("clipboardService"); + _loggerService = ServiceContainer.Resolve("logger"); GroupedItems = new ObservableRangeCollection(); PageTitle = AppResources.Settings; @@ -123,7 +125,7 @@ namespace Bit.App.Pages _showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() && !await _keyConnectorService.GetUsesKeyConnector(); - + _reportLoggingEnabled = await _loggerService.IsEnabled(); BuildList(); } @@ -286,6 +288,26 @@ namespace Bit.App.Pages } } + public async Task LoggerReportingAsync() + { + var options = new[] + { + CreateSelectableOption(AppResources.Yes, _reportLoggingEnabled), + CreateSelectableOption(AppResources.No, !_reportLoggingEnabled), + }; + + var selection = await Page.DisplayActionSheet(AppResources.ReportCrashLogsDescription, AppResources.Cancel, null, options); + + if (selection == null || selection == AppResources.Cancel) + { + return; + } + + await _loggerService.SetEnabled(CompareSelection(selection, AppResources.Yes)); + _reportLoggingEnabled = await _loggerService.IsEnabled(); + BuildList(); + } + public async Task VaultTimeoutActionAsync() { var options = _vaultTimeoutActions.Select(o => @@ -494,11 +516,19 @@ namespace Bit.App.Pages toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg }); toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault }); } + var otherItems = new List { new SettingsPageListItem { Name = AppResources.Options }, new SettingsPageListItem { Name = AppResources.About }, new SettingsPageListItem { Name = AppResources.HelpAndFeedback }, +#if !FDROID + new SettingsPageListItem + { + Name = AppResources.ReportCrashLogs, + SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled, + }, +#endif new SettingsPageListItem { Name = AppResources.RateTheApp }, new SettingsPageListItem { Name = AppResources.DeleteAccount } }; @@ -576,5 +606,9 @@ namespace Bit.App.Pages { return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value; } + + private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option; + + private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}"; } } diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index def2096b2..723d00683 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -3892,5 +3892,17 @@ namespace Bit.App.Resources { return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture); } } + + public static string ReportCrashLogs { + get { + return ResourceManager.GetString("ReportCrashLogs", resourceCulture); + } + } + + public static string ReportCrashLogsDescription { + get { + return ResourceManager.GetString("ReportCrashLogsDescription", resourceCulture); + } + } } } diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 1c0448e6a..20b8aeae3 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -2181,4 +2181,10 @@ Enter the verification code that was sent to your email + + Report crash logs + + + Help Bitwarden improve app stability by allowing crash reports. + diff --git a/src/Core/Abstractions/ILogger.cs b/src/Core/Abstractions/ILogger.cs index 704fca758..fbb0d50a2 100644 --- a/src/Core/Abstractions/ILogger.cs +++ b/src/Core/Abstractions/ILogger.cs @@ -1,11 +1,29 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading.Tasks; namespace Bit.Core.Abstractions { public interface ILogger { + /// + /// Place necessary code to initiaze logger + /// + /// + Task InitAsync(); + + /// + /// Returns if the current logger is enable or disable. + /// + /// + Task IsEnabled(); + + /// + /// Changes the state of the current logger. Setting state enabled to false will block logging. + /// + Task SetEnabled(bool value); + /// /// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported /// and looked into. diff --git a/src/Core/Services/Logging/DebugLogger.cs b/src/Core/Services/Logging/DebugLogger.cs index e70fbda80..a52c1de5d 100644 --- a/src/Core/Services/Logging/DebugLogger.cs +++ b/src/Core/Services/Logging/DebugLogger.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Bit.Core.Abstractions; namespace Bit.Core.Services @@ -45,6 +46,12 @@ namespace Bit.Core.Services } public void Exception(Exception ex) => Debug.WriteLine(ex); + + public Task InitAsync() => Task.CompletedTask; + + public Task IsEnabled() => Task.FromResult(true); + + public Task SetEnabled(bool value) => Task.CompletedTask; } } #endif diff --git a/src/Core/Services/Logging/Logger.cs b/src/Core/Services/Logging/Logger.cs index 69f781435..d22af6df5 100644 --- a/src/Core/Services/Logging/Logger.cs +++ b/src/Core/Services/Logging/Logger.cs @@ -5,13 +5,24 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using Microsoft.AppCenter; using Microsoft.AppCenter.Crashes; +using Newtonsoft.Json; namespace Bit.Core.Services { public class Logger : ILogger { + private const string iOSAppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3"; + private const string DroidAppSecret = "d3834185-b4a6-4347-9047-b86c65293d42"; + + private string _userId; + private string _appId; + private bool _isInitialised = false; + static ILogger _instance; public static ILogger Instance { @@ -29,6 +40,60 @@ namespace Bit.Core.Services { } + + public string Description + { + get + { + return JsonConvert.SerializeObject(new + { + AppId = _appId, + UserId = _userId + }, Formatting.Indented); + } + } + + public async Task InitAsync() + { + if (_isInitialised) + { + return; + } + + var device = ServiceContainer.Resolve("platformUtilsService").GetDevice(); + _userId = await ServiceContainer.Resolve("stateService").GetActiveUserIdAsync(); + _appId = await ServiceContainer.Resolve("appIdService").GetAppIdAsync(); + + switch (device) + { + case Enums.DeviceType.Android: + AppCenter.Start(DroidAppSecret, typeof(Crashes)); + break; + case Enums.DeviceType.iOS: + AppCenter.Start(iOSAppSecret, typeof(Crashes)); + break; + default: + throw new AppCenterException("Cannot start AppCenter. Device type is not configured."); + + } + + AppCenter.SetUserId(_userId); + + Crashes.GetErrorAttachments = (ErrorReport report) => + { + return new ErrorAttachmentLog[] + { + ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"), + }; + }; + + _isInitialised = true; + } + + public async Task IsEnabled() => await AppCenter.IsEnabledAsync(); + + public async Task SetEnabled(bool value) => await AppCenter.SetEnabledAsync(value); + public void Error(string message, IDictionary extraData = null, [CallerMemberName] string memberName = "", diff --git a/src/Core/Services/Logging/LoggerHelper.cs b/src/Core/Services/Logging/LoggerHelper.cs index cfce40af6..9cdf225f6 100644 --- a/src/Core/Services/Logging/LoggerHelper.cs +++ b/src/Core/Services/Logging/LoggerHelper.cs @@ -1,9 +1,6 @@ using System; using Bit.Core.Abstractions; using Bit.Core.Utilities; -#if !FDROID -using Microsoft.AppCenter.Crashes; -#endif namespace Bit.Core.Services { @@ -25,8 +22,9 @@ namespace Bit.Core.Services #if !FDROID // just in case the caller throws the exception in a moment where the logger can't be resolved // we need to track the error as well - Crashes.TrackError(ex); + Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); #endif + } } } diff --git a/src/Core/Services/Logging/StubLogger.cs b/src/Core/Services/Logging/StubLogger.cs index aa3db6aff..2bb8bcd45 100644 --- a/src/Core/Services/Logging/StubLogger.cs +++ b/src/Core/Services/Logging/StubLogger.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Bit.Core.Abstractions; namespace Bit.Core.Services @@ -17,5 +18,11 @@ namespace Bit.Core.Services public void Exception(Exception ex) { } + + public Task InitAsync() => Task.CompletedTask; + + public Task IsEnabled() => Task.FromResult(false); + + public Task SetEnabled(bool value) => Task.CompletedTask; } } diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index e0e529bdb..e8ae87b4b 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -20,7 +20,6 @@ namespace Bit.iOS.Autofill public partial class CredentialProviderViewController : ASCredentialProviderViewController { private Context _context; - private bool _initedAppCenter; private NFCNdefReaderSession _nfcSession = null; private Core.NFCReaderDelegate _nfcDelegate = null; @@ -330,11 +329,7 @@ namespace Bit.iOS.Autofill var messagingService = ServiceContainer.Resolve("messagingService"); ServiceContainer.Init(deviceActionService.DeviceUserAgent, Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys); - if (!_initedAppCenter) - { - iOSCoreHelpers.RegisterAppCenter(); - _initedAppCenter = true; - } + iOSCoreHelpers.InitLogger(); iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); diff --git a/src/iOS.Core/Utilities/AppCenterHelper.cs b/src/iOS.Core/Utilities/AppCenterHelper.cs deleted file mode 100644 index ff19b62a5..000000000 --- a/src/iOS.Core/Utilities/AppCenterHelper.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Bit.Core.Abstractions; -using System.Threading.Tasks; -using Microsoft.AppCenter; -using Microsoft.AppCenter.Crashes; -using Newtonsoft.Json; - -namespace Bit.iOS.Core.Utilities -{ - public class AppCenterHelper - { - private const string AppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3"; - - private readonly IAppIdService _appIdService; - private readonly IStateService _stateService; - - private string _userId; - private string _appId; - - public AppCenterHelper( - IAppIdService appIdService, - IStateService stateService) - { - _appIdService = appIdService; - _stateService = stateService; - } - - public async Task InitAsync() - { - _userId = await _stateService.GetActiveUserIdAsync(); - _appId = await _appIdService.GetAppIdAsync(); - - AppCenter.Start(AppSecret, typeof(Crashes)); - AppCenter.SetUserId(_userId); - - Crashes.GetErrorAttachments = (ErrorReport report) => - { - return new ErrorAttachmentLog[] - { - ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"), - }; - }; - } - - public string Description - { - get - { - return JsonConvert.SerializeObject(new - { - AppId = _appId, - UserId = _userId - }, Formatting.Indented); - } - } - } -} diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index 26190c20b..c1b7988bc 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -25,14 +25,9 @@ namespace Bit.iOS.Core.Utilities public static string AppGroupId = "group.com.8bit.bitwarden"; public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden"; - public static void RegisterAppCenter() + public static void InitLogger() { -#if !DEBUG - var appCenterHelper = new AppCenterHelper( - ServiceContainer.Resolve("appIdService"), - ServiceContainer.Resolve("stateService")); - var appCenterTask = appCenterHelper.InitAsync(); -#endif + ServiceContainer.Resolve("logger").InitAsync(); } public static void RegisterLocalServices() diff --git a/src/iOS.Core/iOS.Core.csproj b/src/iOS.Core/iOS.Core.csproj index 823b2bacd..044539d7a 100644 --- a/src/iOS.Core/iOS.Core.csproj +++ b/src/iOS.Core/iOS.Core.csproj @@ -172,7 +172,6 @@ - diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index a7b6cf484..8fcf0da27 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -26,7 +26,6 @@ namespace Bit.iOS.Extension public partial class LoadingViewController : ExtendedUIViewController { private Context _context = new Context(); - private bool _initedAppCenter; private NFCNdefReaderSession _nfcSession = null; private Core.NFCReaderDelegate _nfcDelegate = null; @@ -408,11 +407,7 @@ namespace Bit.iOS.Extension var messagingService = ServiceContainer.Resolve("messagingService"); ServiceContainer.Init(deviceActionService.DeviceUserAgent, Bit.Core.Constants.iOSExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys); - if (!_initedAppCenter) - { - iOSCoreHelpers.RegisterAppCenter(); - _initedAppCenter = true; - } + iOSCoreHelpers.InitLogger(); iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); diff --git a/src/iOS.ShareExtension/LoadingViewController.cs b/src/iOS.ShareExtension/LoadingViewController.cs index c1a15d1cf..6411ac175 100644 --- a/src/iOS.ShareExtension/LoadingViewController.cs +++ b/src/iOS.ShareExtension/LoadingViewController.cs @@ -9,6 +9,7 @@ using Bit.App.Pages; using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Enums; +using Bit.Core.Services; using Bit.Core.Utilities; using Bit.iOS.Core; using Bit.iOS.Core.Controllers; @@ -17,7 +18,6 @@ using Bit.iOS.Core.Views; using Bit.iOS.ShareExtension.Models; using CoreNFC; using Foundation; -using Microsoft.AppCenter.Crashes; using MobileCoreServices; using UIKit; using Xamarin.Forms; @@ -27,7 +27,6 @@ namespace Bit.iOS.ShareExtension public partial class LoadingViewController : ExtendedUIViewController { private Context _context = new Context(); - private bool _initedAppCenter; private NFCNdefReaderSession _nfcSession = null; private Core.NFCReaderDelegate _nfcDelegate = null; @@ -99,7 +98,7 @@ namespace Bit.iOS.ShareExtension } catch (Exception ex) { - Crashes.TrackError(ex); + LoggerHelper.LogEvenIfCantBeResolved(ex); } } @@ -216,11 +215,7 @@ namespace Bit.iOS.ShareExtension var messagingService = ServiceContainer.Resolve("messagingService"); ServiceContainer.Init(_deviceActionService.Value.DeviceUserAgent, Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys); - if (!_initedAppCenter) - { - iOSCoreHelpers.RegisterAppCenter(); - _initedAppCenter = true; - } + iOSCoreHelpers.InitLogger(); iOSCoreHelpers.Bootstrap(); var app = new App.App(new AppOptions { IosExtension = true }); diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index ad46ba1cf..020a3803b 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -294,7 +294,7 @@ namespace Bit.iOS var deviceActionService = ServiceContainer.Resolve("deviceActionService"); ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey, Constants.iOSAllClearCipherCacheKeys); - iOSCoreHelpers.RegisterAppCenter(); + iOSCoreHelpers.InitLogger(); _pushHandler = new iOSPushNotificationHandler( ServiceContainer.Resolve("pushNotificationListenerService")); _nfcDelegate = new Core.NFCReaderDelegate((success, message) => From a259560d290aca772793be03dcb3e76ca22a8c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Mon, 23 May 2022 10:20:48 +0100 Subject: [PATCH 087/100] PS-592 Mobile - Name field is not prioritised in search results (#1907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Search method now gives priority to matches in the Name property Co-authored-by: André Bispo --- src/Core/Services/SearchService.cs | 48 +++++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Core/Services/SearchService.cs b/src/Core/Services/SearchService.cs index 09610942e..8a5b6ac7d 100644 --- a/src/Core/Services/SearchService.cs +++ b/src/Core/Services/SearchService.cs @@ -74,28 +74,34 @@ namespace Bit.Core.Services CancellationToken ct = default, bool deleted = false) { ct.ThrowIfCancellationRequested(); + var matchedCiphers = new List(); + var lowPriorityMatchedCiphers = new List(); query = query.Trim().ToLower(); - return ciphers.Where(c => + + foreach (var c in ciphers) { ct.ThrowIfCancellationRequested(); if (c.Name?.ToLower().Contains(query) ?? false) { - return true; + matchedCiphers.Add(c); } - if (query.Length >= 8 && c.Id.StartsWith(query)) + else if (query.Length >= 8 && c.Id.StartsWith(query)) { - return true; + lowPriorityMatchedCiphers.Add(c); } - if (c.SubTitle?.ToLower().Contains(query) ?? false) + else if (c.SubTitle?.ToLower().Contains(query) ?? false) { - return true; + lowPriorityMatchedCiphers.Add(c); } - if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false) + else if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false) { - return true; + lowPriorityMatchedCiphers.Add(c); } - return false; - }).ToList(); + } + + ct.ThrowIfCancellationRequested(); + matchedCiphers.AddRange(lowPriorityMatchedCiphers); + return matchedCiphers; } public async Task> SearchSendsAsync(string query, Func filter = null, @@ -133,25 +139,31 @@ namespace Bit.Core.Services public List SearchSendsBasic(List sends, string query, CancellationToken ct = default, bool deleted = false) { + var matchedSends = new List(); + var lowPriorityMatchSends = new List(); ct.ThrowIfCancellationRequested(); query = query.Trim().ToLower(); - return sends.Where(s => + + foreach (var s in sends) { ct.ThrowIfCancellationRequested(); if (s.Name?.ToLower().Contains(query) ?? false) { - return true; + matchedSends.Add(s); } - if (s.Text?.Text?.ToLower().Contains(query) ?? false) + else if (s.Text?.Text?.ToLower().Contains(query) ?? false) { - return true; + lowPriorityMatchSends.Add(s); } - if (s.File?.FileName?.ToLower()?.Contains(query) ?? false) + else if (s.File?.FileName?.ToLower()?.Contains(query) ?? false) { - return true; + lowPriorityMatchSends.Add(s); } - return false; - }).ToList(); + } + + ct.ThrowIfCancellationRequested(); + matchedSends.AddRange(lowPriorityMatchSends); + return matchedSends; } } } From 58d7b001a5bc8459bf2d4b40fe952b03a64506b8 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Mon, 23 May 2022 16:01:04 -0400 Subject: [PATCH 088/100] add master password reprompt to share sheet extension (#1922) --- src/iOS.Extension/LoginListViewController.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/iOS.Extension/LoginListViewController.cs b/src/iOS.Extension/LoginListViewController.cs index ea7917c76..3df4654d4 100644 --- a/src/iOS.Extension/LoginListViewController.cs +++ b/src/iOS.Extension/LoginListViewController.cs @@ -9,6 +9,8 @@ using MobileCoreServices; using Bit.iOS.Core.Controllers; using Bit.App.Resources; using Bit.iOS.Core.Views; +using Bit.App.Abstractions; +using Bit.Core.Utilities; namespace Bit.iOS.Extension { @@ -18,10 +20,12 @@ namespace Bit.iOS.Extension : base(handle) { DismissModalAction = Cancel; + PasswordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); } public Context Context { get; set; } public LoadingViewController LoadingController { get; set; } + public IPasswordRepromptService PasswordRepromptService { get; private set; } public async override void ViewDidLoad() { @@ -104,7 +108,7 @@ namespace Bit.iOS.Extension _controller = controller; } - public override void RowSelected(UITableView tableView, NSIndexPath indexPath) + public async override void RowSelected(UITableView tableView, NSIndexPath indexPath) { tableView.DeselectRow(indexPath, true); tableView.EndEditing(true); @@ -122,6 +126,11 @@ namespace Bit.iOS.Extension return; } + if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await _controller.PasswordRepromptService.ShowPasswordPromptAsync()) + { + return; + } + if (_controller.CanAutoFill() && !string.IsNullOrWhiteSpace(item.Password)) { string totp = null; From 7e9b7398c8564b197c149781a543b105481b2b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Tue, 24 May 2022 15:09:24 +0100 Subject: [PATCH 089/100] PS-90: App keeps asking for 2FA even though I've checked the "remember me" checkbox (#1921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Logout was removing the 2FA token, portion of code deleted. - Clear saved token when 2FA error is returned by the server. Co-authored-by: André Bispo --- src/Core/Services/AuthService.cs | 1 + src/Core/Services/StateService.cs | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 3e6517f82..c1fb38fa8 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -346,6 +346,7 @@ namespace Bit.Core.Services TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2; result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2; CaptchaToken = response.TwoFactorResponse.CaptchaToken; + await _tokenService.ClearTwoFactorTokenAsync(email); return result; } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index ea70d64e7..23d8de670 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1372,10 +1372,6 @@ namespace Bit.Core.Services await SetEncryptedPasswordGenerationHistoryAsync(null, userId); await SetEncryptedSendsAsync(null, userId); await SetSettingsAsync(null, userId); - if (!string.IsNullOrWhiteSpace(email)) - { - await SetTwoFactorTokenAsync(null, email); - } if (userInitiated) { From 43e9515a032e4cc60252cd6a96107fe6fb1d0282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Wed, 25 May 2022 17:20:51 +0100 Subject: [PATCH 090/100] [PS-672] Accessibility - File/Text controls unintuitive, "Text" accessible name incorrect (#1923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PS-672: Accessibility - File/Text controls unintuitive, "Text" accessible name incorrect - Added new text to help identify File / Text segmented button - Added missing text for Text button accessibility * PS-672: refactor code with pr suggestions * PS-672: removed unnecessary code * PS-672: change text from "click" to "tap" * PS-672: removed comma Co-authored-by: André Bispo --- src/App/Pages/Send/SendAddEditPage.xaml | 2 ++ .../Pages/Send/SendAddEditPageViewModel.cs | 4 +++ src/App/Resources/AppResources.Designer.cs | 30 +++++++++++++++++++ src/App/Resources/AppResources.resx | 15 ++++++++++ 4 files changed, 51 insertions(+) diff --git a/src/App/Pages/Send/SendAddEditPage.xaml b/src/App/Pages/Send/SendAddEditPage.xaml index fc3009e07..c6701c682 100644 --- a/src/App/Pages/Send/SendAddEditPage.xaml +++ b/src/App/Pages/Send/SendAddEditPage.xaml @@ -121,6 +121,7 @@ Clicked="FileType_Clicked" AutomationProperties.IsInAccessibleTree="True" AutomationProperties.Name="{u:I18n File}" + AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}" Grid.Column="0"> diff --git a/src/App/Pages/Send/SendAddEditPageViewModel.cs b/src/App/Pages/Send/SendAddEditPageViewModel.cs index 01d588170..99c41f6a2 100644 --- a/src/App/Pages/Send/SendAddEditPageViewModel.cs +++ b/src/App/Pages/Send/SendAddEditPageViewModel.cs @@ -44,6 +44,8 @@ namespace Bit.App.Pages { nameof(IsText), nameof(IsFile), + nameof(FileTypeAccessibilityLabel), + nameof(TextTypeAccessibilityLabel) }; private bool _disableHideEmail; private bool _sendOptionsPolicyInEffect; @@ -231,6 +233,8 @@ namespace Bit.App.Pages public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6; public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; + public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected; + public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected; public async Task InitAsync() { diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 723d00683..ba004805d 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -3299,6 +3299,12 @@ namespace Bit.App.Resources { } } + public static string Text { + get { + return ResourceManager.GetString("Text", resourceCulture); + } + } + public static string TypeText { get { return ResourceManager.GetString("TypeText", resourceCulture); @@ -3329,6 +3335,30 @@ namespace Bit.App.Resources { } } + public static string FileTypeIsSelected { + get { + return ResourceManager.GetString("FileTypeIsSelected", resourceCulture); + } + } + + public static string FileTypeIsNotSelected { + get { + return ResourceManager.GetString("FileTypeIsNotSelected", resourceCulture); + } + } + + public static string TextTypeIsSelected { + get { + return ResourceManager.GetString("TextTypeIsSelected", resourceCulture); + } + } + + public static string TextTypeIsNotSelected { + get { + return ResourceManager.GetString("TextTypeIsNotSelected", resourceCulture); + } + } + public static string DeletionDate { get { return ResourceManager.GetString("DeletionDate", resourceCulture); diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 20b8aeae3..ab695df1f 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1861,6 +1861,9 @@ A friendly name to describe this Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. + + Text + Text @@ -1877,6 +1880,18 @@ The file you want to send. + + File type is selected. + + + File type is not selected, tap to select. + + + Text type is selected. + + + Text type is not selected, tap to select. + Deletion Date From 5272c996435246ccca5c026636edf6d48d9b2d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Filipe=20da=20Silva=20Bispo?= Date: Wed, 25 May 2022 17:25:21 +0100 Subject: [PATCH 091/100] PS-674: Accessibility password generator toggles (#1924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Additional information for VoiceOver and TalkBack - Added new labels Co-authored-by: André Bispo --- src/App/Pages/Generator/GeneratorPage.xaml | 32 ++++++++++++++++------ src/App/Resources/AppResources.Designer.cs | 24 ++++++++++++++++ src/App/Resources/AppResources.resx | 12 ++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/App/Pages/Generator/GeneratorPage.xaml b/src/App/Pages/Generator/GeneratorPage.xaml index 2c581b8f5..acaf7aacc 100644 --- a/src/App/Pages/Generator/GeneratorPage.xaml +++ b/src/App/Pages/Generator/GeneratorPage.xaml @@ -183,52 +183,68 @@