diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5e06fa5de..3ff754a54 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,12 +20,15 @@ src/watchOS @bitwarden/team-vault-dev ## Tools team files ## src/Core/Services/EmailForwarders @bitwarden/team-tools-dev - ## Crowdin Sync files ## -src/App/Resources @bitwarden/tech-leads -src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/tech-leads +src/App/Resources @bitwarden/team-tools-dev +src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev +store/apple @bitwarden/team-tools-dev +store/google @bitwarden/team-tools-dev ## Locales ## src/App/Resources/AppResources.Designer.cs src/App/Resources/AppResources.resx src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj +store/apple/en +store/google/en diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 74639f2b3..658ccb27d 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -24,7 +24,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9182f7d5..4cb21b8ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,7 +42,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/release-version-check@main with: release-type: ${{ github.event.inputs.release_type }} project-type: xamarin diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 43cb6007e..d48253d1a 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -28,7 +28,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" @@ -45,31 +45,31 @@ jobs: run: git switch -c version_bump_${{ github.event.inputs.version_number }} - name: Bump Version - Android XML - uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/version-bump@main with: version: ${{ github.event.inputs.version_number }} file_path: "./src/Android/Properties/AndroidManifest.xml" - name: Bump Version - iOS.Autofill - uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/version-bump@main with: version: ${{ github.event.inputs.version_number }} file_path: "./src/iOS.Autofill/Info.plist" - name: Bump Version - iOS.Extension - uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/version-bump@main with: version: ${{ github.event.inputs.version_number }} file_path: "./src/iOS.Extension/Info.plist" - name: Bump Version - iOS.ShareExtension - uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/version-bump@main with: version: ${{ github.event.inputs.version_number }} file_path: "./src/iOS.ShareExtension/Info.plist" - name: Bump Version - iOS - uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/version-bump@main with: version: ${{ github.event.inputs.version_number }} file_path: "./src/iOS/Info.plist" diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index 25e35348b..fc1db4d39 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -8,4 +8,4 @@ on: jobs: call-workflow: - uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67 + uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs index aaf20b76f..ddf6a28fc 100644 --- a/src/Core/Abstractions/IApiService.cs +++ b/src/Core/Abstractions/IApiService.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Bit.Core.Enums; -using Bit.Core.Models.Domain; +using Bit.Core.Models.Data; using Bit.Core.Models.Request; using Bit.Core.Models.Response; using DeviceType = Bit.Core.Enums.DeviceType; @@ -52,7 +51,7 @@ namespace Bit.Core.Abstractions Task SendAsync(HttpMethod method, string path, TRequest body, bool authed, bool hasResponse, Action alterRequest, bool logoutOnUnauthorized = true, bool sendToIdentity = false); Task SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default); - void SetUrls(EnvironmentUrls urls); + void SetUrls(EnvironmentUrlData urls); [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.")] Task PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data); Task PostCipherAttachmentAsync(string id, AttachmentRequest request); diff --git a/src/Core/Abstractions/IEnvironmentService.cs b/src/Core/Abstractions/IEnvironmentService.cs index 7469452b9..f418bc12c 100644 --- a/src/Core/Abstractions/IEnvironmentService.cs +++ b/src/Core/Abstractions/IEnvironmentService.cs @@ -1,5 +1,5 @@ -using System.Threading.Tasks; -using Bit.Core.Models.Data; +using Bit.Core.Models.Data; +using BwRegion = Bit.Core.Enums.Region; namespace Bit.Core.Abstractions { @@ -12,10 +12,12 @@ namespace Bit.Core.Abstractions string NotificationsUrl { get; set; } string WebVaultUrl { get; set; } string EventsUrl { get; set; } + BwRegion SelectedRegion { get; set; } string GetWebVaultUrl(bool returnNullIfDefault = false); string GetWebSendUrl(); - Task SetUrlsAsync(EnvironmentUrlData urls); + string GetCurrentDomain(); Task SetUrlsFromStorageAsync(); + Task SetRegionAsync(BwRegion region, EnvironmentUrlData selfHostedUrls = null); } } diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index 07ce65461..64ed18855 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; using Bit.Core.Models.View; -using Bit.Core.Services; +using BwRegion = Bit.Core.Enums.Region; namespace Bit.Core.Abstractions { @@ -185,6 +182,10 @@ namespace Bit.Core.Abstractions void SetConfigs(ConfigResponse value); Task GetShouldTrustDeviceAsync(); Task SetShouldTrustDeviceAsync(bool value); + Task SetUserHasMasterPasswordAsync(bool value, string userId = null); + Task GetActiveUserRegionAsync(); + Task GetPreAuthRegionAsync(); + Task SetPreAuthRegionAsync(BwRegion value); [Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")] Task GetPinProtectedAsync(string userId = null); [Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")] diff --git a/src/Core/App.xaml.cs b/src/Core/App.xaml.cs index f8de29eb3..b9a14d5e6 100644 --- a/src/Core/App.xaml.cs +++ b/src/Core/App.xaml.cs @@ -178,6 +178,11 @@ namespace Bit.App new NavigationPage(new UpdateTempPasswordPage())); }); } + else if (message.Command == Constants.ForceSetPassword) + { + await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync( + new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)))); + } else if (message.Command == "syncCompleted") { await _configService.GetAsync(true); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 04641a6b0..9b96deb4a 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -46,6 +46,7 @@ namespace Bit.Core public const string PreLoginEmailKey = "preLoginEmailKey"; public const string ConfigsKey = "configsKey"; public const string DisplayEuEnvironmentFlag = "display-eu-environment"; + public const string RegionEnvironment = "regionEnvironment"; /// /// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in @@ -56,6 +57,7 @@ namespace Bit.Core public const string AppLocaleKey = "appLocale"; public const string ClearSensitiveFields = "clearSensitiveFields"; public const string ForceUpdatePassword = "forceUpdatePassword"; + public const string ForceSetPassword = "forceSetPassword"; public const string ShouldTrustDevice = "shouldTrustDevice"; public const int SelectFileRequestCode = 42; public const int SelectFilePermissionRequestCode = 43; diff --git a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs index 5a71e90b7..c9fae9e6d 100644 --- a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs +++ b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs @@ -1,7 +1,7 @@ using System.Windows.Input; -using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Controls { @@ -37,17 +37,14 @@ namespace Bit.App.Controls { InitializeComponent(); - ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync, - onException: ex => _logger.Value.Exception(ex), - allowsMultipleExecutions: false); + ToggleVisibililtyCommand = new AsyncRelayCommand(ToggleVisibilityAsync, + AsyncRelayCommandOptions.None); - SelectAccountCommand = new AsyncCommand(SelectAccountAsync, - onException: ex => _logger.Value.Exception(ex), - allowsMultipleExecutions: false); + SelectAccountCommand = new AsyncRelayCommand(SelectAccountAsync, + AsyncRelayCommandOptions.None); - LongPressAccountCommand = new AsyncCommand(LongPressAccountAsync, - onException: ex => _logger.Value.Exception(ex), - allowsMultipleExecutions: false); + LongPressAccountCommand = new AsyncRelayCommand(LongPressAccountAsync, + AsyncRelayCommandOptions.None); } public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel; @@ -70,13 +67,20 @@ namespace Bit.App.Controls public async Task ToggleVisibilityAsync() { - if (IsVisible) + try { - await HideAsync(); + if (IsVisible) + { + await HideAsync(); + } + else + { + await ShowAsync(); + } } - else + catch (Exception ex) { - await ShowAsync(); + _logger.Value.Exception(ex); } } @@ -172,12 +176,13 @@ namespace Bit.App.Controls private async Task LongPressAccountAsync(AccountViewCellViewModel item) { - if (!LongPressAccountEnabled || item == null || !item.IsAccount) - { - return; - } try { + if (!LongPressAccountEnabled || item == null || !item.IsAccount) + { + return; + } + await Task.Delay(100); await HideAsync(); diff --git a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs index fde0cdea0..9a3a44805 100644 --- a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs +++ b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs @@ -17,11 +17,11 @@ namespace Bit.App.Controls _stateService = stateService; _messagingService = messagingService; - SelectAccountCommand = new AsyncCommand(SelectAccountAsync, + SelectAccountCommand = CreateDefaultAsyncRelayCommand(SelectAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); - LongPressAccountCommand = new AsyncCommand>(LongPressAccountAsync, + LongPressAccountCommand = CreateDefaultAsyncRelayCommand>(LongPressAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Controls/Settings/BaseSettingControlView.cs b/src/Core/Controls/Settings/BaseSettingControlView.cs index add5db878..4597e37e9 100644 --- a/src/Core/Controls/Settings/BaseSettingControlView.cs +++ b/src/Core/Controls/Settings/BaseSettingControlView.cs @@ -6,10 +6,10 @@ namespace Bit.App.Controls public class BaseSettingItemView : ContentView { public static readonly BindableProperty TitleProperty = BindableProperty.Create( - nameof(Title), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay); + nameof(Title), typeof(string), typeof(SwitchItemView), null); public static readonly BindableProperty SubtitleProperty = BindableProperty.Create( - nameof(Subtitle), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay); + nameof(Subtitle), typeof(string), typeof(SwitchItemView), null); public string Title { diff --git a/src/Core/Controls/Settings/SettingChooserItemView.xaml.cs b/src/Core/Controls/Settings/SettingChooserItemView.xaml.cs index a1daa3fe1..dabf115de 100644 --- a/src/Core/Controls/Settings/SettingChooserItemView.xaml.cs +++ b/src/Core/Controls/Settings/SettingChooserItemView.xaml.cs @@ -7,7 +7,7 @@ namespace Bit.App.Controls public partial class SettingChooserItemView : BaseSettingItemView { public static readonly BindableProperty DisplayValueProperty = BindableProperty.Create( - nameof(DisplayValue), typeof(string), typeof(SettingChooserItemView), null, BindingMode.OneWay); + nameof(DisplayValue), typeof(string), typeof(SettingChooserItemView), null); public static readonly BindableProperty ChooseCommandProperty = BindableProperty.Create( nameof(ChooseCommand), typeof(ICommand), typeof(ExternalLinkItemView)); diff --git a/src/Core/Enums/Region.cs b/src/Core/Enums/Region.cs new file mode 100644 index 000000000..543ef2f47 --- /dev/null +++ b/src/Core/Enums/Region.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Enums +{ + public enum Region + { + US, + EU, + SelfHosted + } +} + diff --git a/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs b/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs index ad8ad96f4..526e0dc96 100644 --- a/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs +++ b/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs @@ -30,7 +30,7 @@ namespace Bit.App.Lists.ItemViewModels.CustomFields _eventService = eventService; CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field)); - ToggleHiddenValueCommand = new AsyncCommand(ToggleHiddenValueAsync, null, ex => + ToggleHiddenValueCommand = CreateDefaultAsyncRelayCommand(ToggleHiddenValueAsync, null, ex => { //#if !FDROID // Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); diff --git a/src/Core/Models/Data/EnvironmentUrlData.cs b/src/Core/Models/Data/EnvironmentUrlData.cs index ae57f804b..04e9053c1 100644 --- a/src/Core/Models/Data/EnvironmentUrlData.cs +++ b/src/Core/Models/Data/EnvironmentUrlData.cs @@ -1,9 +1,34 @@ -namespace Bit.Core.Models.Data +using System.Text.RegularExpressions; +using Bit.Core.Utilities; +using BwRegion = Bit.Core.Enums.Region; + +namespace Bit.Core.Models.Data { public class EnvironmentUrlData { - public static EnvironmentUrlData DefaultUS = new EnvironmentUrlData { Base = "https://vault.bitwarden.com" }; - public static EnvironmentUrlData DefaultEU = new EnvironmentUrlData { Base = "https://vault.bitwarden.eu" }; + public static EnvironmentUrlData DefaultUS = new EnvironmentUrlData + { + Base = "https://vault.bitwarden.com", + Api = "https://api.bitwarden.com", + Identity = "https://identity.bitwarden.com", + Icons = "https://icons.bitwarden.net", + WebVault = "https://vault.bitwarden.com", + Notifications = "https://notifications.bitwarden.com", + Events = "https://events.bitwarden.com", + Domain = "bitwarden.com" + }; + + public static EnvironmentUrlData DefaultEU = new EnvironmentUrlData + { + Base = "https://vault.bitwarden.eu", + Api = "https://api.bitwarden.eu", + Identity = "https://identity.bitwarden.eu", + Icons = "https://icons.bitwarden.eu", + WebVault = "https://vault.bitwarden.eu", + Notifications = "https://notifications.bitwarden.eu", + Events = "https://events.bitwarden.eu", + Domain = "bitwarden.eu" + }; public string Base { get; set; } public string Api { get; set; } @@ -12,6 +37,7 @@ public string Notifications { get; set; } public string WebVault { get; set; } public string Events { get; set; } + public string Domain { get; set; } public bool IsEmpty => string.IsNullOrEmpty(Base) && string.IsNullOrEmpty(Api) @@ -20,5 +46,63 @@ && string.IsNullOrEmpty(Notifications) && string.IsNullOrEmpty(WebVault) && string.IsNullOrEmpty(Events); + + public BwRegion Region + { + get + { + if (Base == BwRegion.US.BaseUrl()) + { + return BwRegion.US; + } + if (Base == BwRegion.EU.BaseUrl()) + { + return BwRegion.EU; + } + return BwRegion.SelfHosted; + } + } + + public EnvironmentUrlData FormatUrls() + { + return new EnvironmentUrlData + { + Base = FormatUrl(Base), + Api = FormatUrl(Api), + Identity = FormatUrl(Identity), + Icons = FormatUrl(Icons), + Notifications = FormatUrl(Notifications), + WebVault = FormatUrl(WebVault), + Events = FormatUrl(Events) + }; + } + + private string FormatUrl(string url) + { + if (string.IsNullOrWhiteSpace(url)) + { + return null; + } + url = Regex.Replace(url, "\\/+$", string.Empty); + if (!url.StartsWith("http://") && !url.StartsWith("https://")) + { + url = string.Concat("https://", url); + } + return url.Trim(); + } + + public string GetDomainOrHostname() + { + var url = WebVault ?? Base ?? Api ?? Identity; + if (string.IsNullOrWhiteSpace(url)) + { + return string.Empty; + } + if (url.Contains(BwRegion.US.Domain()) || url.Contains(BwRegion.EU.Domain())) + { + return CoreHelpers.GetDomain(url); + } + return CoreHelpers.GetHostname(url); + } } } diff --git a/src/Core/Models/Data/Permissions.cs b/src/Core/Models/Data/Permissions.cs index c1b8278f2..923039a4d 100644 --- a/src/Core/Models/Data/Permissions.cs +++ b/src/Core/Models/Data/Permissions.cs @@ -15,5 +15,6 @@ public bool ManagePolicies { get; set; } public bool ManageSso { get; set; } public bool ManageUsers { get; set; } + public bool ManageResetPassword { get; set; } } } diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs index 3377d60a7..99816f2ef 100644 --- a/src/Core/Models/Domain/Account.cs +++ b/src/Core/Models/Domain/Account.cs @@ -1,6 +1,6 @@ -using System; -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Models.Data; +using BwRegion = Bit.Core.Enums.Region; namespace Bit.Core.Models.Domain { @@ -102,12 +102,14 @@ namespace Bit.Core.Models.Domain return; } + Region = copy.Region; EnvironmentUrls = copy.EnvironmentUrls; VaultTimeout = copy.VaultTimeout; VaultTimeoutAction = copy.VaultTimeoutAction; ScreenCaptureAllowed = copy.ScreenCaptureAllowed; } + public BwRegion? Region; public EnvironmentUrlData EnvironmentUrls; [Obsolete("Feb 10 2023: VaultTimeout has been deprecated in favor of stored prefs to retain value after logout. It remains here to allow for migration during app upgrade.")] public int? VaultTimeout; diff --git a/src/Core/Models/Domain/EnvironmentUrls.cs b/src/Core/Models/Domain/EnvironmentUrls.cs deleted file mode 100644 index 52cfff8d3..000000000 --- a/src/Core/Models/Domain/EnvironmentUrls.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Bit.Core.Models.Domain -{ - public class EnvironmentUrls - { - public string Base { get; set; } - public string Api { get; set; } - public string Identity { get; set; } - public string Events { get; set; } - } -} diff --git a/src/Core/Models/Domain/ForcePasswordResetReason.cs b/src/Core/Models/Domain/ForcePasswordResetReason.cs index 70b701bdd..90a86c78b 100644 --- a/src/Core/Models/Domain/ForcePasswordResetReason.cs +++ b/src/Core/Models/Domain/ForcePasswordResetReason.cs @@ -11,6 +11,12 @@ /// Occurs when a user logs in with a master password that does not meet an organization's master password /// policy that is enforced on login. /// - WeakMasterPasswordOnLogin + WeakMasterPasswordOnLogin, + + /// + /// Occurs when a TDE user without a password obtains the password reset permission. + /// Set post login & decryption client side and by server in sync (to catch logged in users). + /// + TdeUserWithoutPasswordHasPasswordResetPermission, } } diff --git a/src/Core/Models/Domain/UsernameGenerationOptions.cs b/src/Core/Models/Domain/UsernameGenerationOptions.cs index 554783b66..2ca57083e 100644 --- a/src/Core/Models/Domain/UsernameGenerationOptions.cs +++ b/src/Core/Models/Domain/UsernameGenerationOptions.cs @@ -46,7 +46,11 @@ namespace Bit.Core.Models.Domain case ForwardedEmailServiceType.DuckDuckGo: return new ForwarderOptions { ApiKey = DuckDuckGoApiKey }; case ForwardedEmailServiceType.Fastmail: - return new ForwarderOptions { ApiKey = FastMailApiKey }; + return new FastmailForwarderOptions + { + ApiKey = FastMailApiKey, + Website = EmailWebsite + }; case ForwardedEmailServiceType.FirefoxRelay: return new ForwarderOptions { ApiKey = FirefoxRelayApiAccessToken }; case ForwardedEmailServiceType.SimpleLogin: diff --git a/src/Core/Models/Request/SetPasswordRequest.cs b/src/Core/Models/Request/SetPasswordRequest.cs index d41eb2249..b5c6cc086 100644 --- a/src/Core/Models/Request/SetPasswordRequest.cs +++ b/src/Core/Models/Request/SetPasswordRequest.cs @@ -7,7 +7,7 @@ namespace Bit.Core.Models.Request public string MasterPasswordHash { get; set; } public string Key { get; set; } public string MasterPasswordHint { get; set; } - public KeysRequest Keys { get; set; } + public KeysRequest? Keys { get; set; } public KdfType Kdf { get; set; } public int KdfIterations { get; set; } public int? KdfMemory { get; set; } diff --git a/src/Core/Models/Response/ProfileResponse.cs b/src/Core/Models/Response/ProfileResponse.cs index 9abc4584e..35bc1336c 100644 --- a/src/Core/Models/Response/ProfileResponse.cs +++ b/src/Core/Models/Response/ProfileResponse.cs @@ -20,5 +20,6 @@ namespace Bit.Core.Models.Response public List Organizations { get; set; } public bool UsesKeyConnector { get; set; } public string AvatarColor { get; set; } + public bool HasManageResetPasswordPermission { get; set; } } } diff --git a/src/Core/Models/View/AccountView.cs b/src/Core/Models/View/AccountView.cs index 958f1435d..d522a5b49 100644 --- a/src/Core/Models/View/AccountView.cs +++ b/src/Core/Models/View/AccountView.cs @@ -22,21 +22,7 @@ namespace Bit.Core.Models.View Email = a.Profile?.Email; Name = a.Profile?.Name; AvatarColor = a.Profile?.AvatarColor; - Hostname = ParseEndpoint(a.Settings?.EnvironmentUrls); - } - - private string ParseEndpoint(EnvironmentUrlData urls) - { - var url = urls?.WebVault ?? urls?.Base; - if (!string.IsNullOrWhiteSpace(url)) - { - if (url.Contains("bitwarden.com") || url.Contains("bitwarden.eu")) - { - return CoreHelpers.GetDomain(url); - } - return CoreHelpers.GetHostname(url); - } - return string.Empty; + Hostname = a.Settings?.EnvironmentUrls?.GetDomainOrHostname(); } public bool IsAccount { get; set; } diff --git a/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs b/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs index 76175ac34..f003fcbd4 100644 --- a/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs +++ b/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs @@ -1,11 +1,8 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; -using Bit.Core.Models.Data; using Bit.Core.Utilities; -using Bit.App.Utilities; +using BwRegion = Bit.Core.Enums.Region; namespace Bit.App.Pages { @@ -19,14 +16,25 @@ namespace Bit.App.Pages _environmentService = ServiceContainer.Resolve("environmentService"); PageTitle = AppResources.Settings; - BaseUrl = _environmentService.BaseUrl == EnvironmentUrlData.DefaultEU.Base || EnvironmentUrlData.DefaultUS.Base == _environmentService.BaseUrl ? - string.Empty : _environmentService.BaseUrl; + SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: OnSubmitException, allowsMultipleExecutions: false); + Init(); + } + + public void Init() + { + if (_environmentService.SelectedRegion != BwRegion.SelfHosted || + _environmentService.BaseUrl == BwRegion.US.BaseUrl() || + _environmentService.BaseUrl == BwRegion.EU.BaseUrl()) + { + return; + } + + BaseUrl = _environmentService.BaseUrl; WebVaultUrl = _environmentService.WebVaultUrl; ApiUrl = _environmentService.ApiUrl; IdentityUrl = _environmentService.IdentityUrl; IconsUrl = _environmentService.IconsUrl; NotificationsUrls = _environmentService.NotificationsUrl; - SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); } public ICommand SubmitCommand { get; } @@ -46,8 +54,7 @@ namespace Bit.App.Pages await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.EnvironmentPageUrlsError, AppResources.Ok); return; } - - var resUrls = await _environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData + var urls = new Core.Models.Data.EnvironmentUrlData { Base = BaseUrl, Api = ApiUrl, @@ -55,7 +62,8 @@ namespace Bit.App.Pages WebVault = WebVaultUrl, Icons = IconsUrl, Notifications = NotificationsUrls - }); + }; + var resUrls = await _environmentService.SetRegionAsync(urls.Region, urls); // re-set urls since service can change them, ex: prefixing https:// BaseUrl = resUrls.Base; diff --git a/src/Core/Pages/Accounts/HintPageViewModel.cs b/src/Core/Pages/Accounts/HintPageViewModel.cs index 443c35fc1..1640be1a5 100644 --- a/src/Core/Pages/Accounts/HintPageViewModel.cs +++ b/src/Core/Pages/Accounts/HintPageViewModel.cs @@ -1,11 +1,9 @@ -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Bit.App.Utilities; namespace Bit.App.Pages { @@ -25,7 +23,7 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve(); PageTitle = AppResources.PasswordHint; - SubmitCommand = new AsyncCommand(SubmitAsync, + SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: ex => { _logger.Exception(ex); diff --git a/src/Core/Pages/Accounts/HomePage.xaml.cs b/src/Core/Pages/Accounts/HomePage.xaml.cs index 05dcbb7cf..61ddf4950 100644 --- a/src/Core/Pages/Accounts/HomePage.xaml.cs +++ b/src/Core/Pages/Accounts/HomePage.xaml.cs @@ -58,7 +58,6 @@ namespace Bit.App.Pages try { _accountAvatar?.OnAppearing(); - if (!_appOptions?.HideAccountSwitcher ?? false) { await MainThread.InvokeOnMainThreadAsync(async () => _vm.AvatarImageSource = await GetAvatarImageSourceAsync(false)); @@ -71,14 +70,14 @@ namespace Bit.App.Pages } }); - await _vm.UpdateEnvironment(); + await _vm.UpdateEnvironmentAsync(); } catch (Exception ex) { _logger.Value?.Exception(ex); } } - + protected override void OnNavigatingFrom(NavigatingFromEventArgs args) { base.OnNavigatingFrom(args); @@ -89,7 +88,7 @@ namespace Bit.App.Pages protected override bool OnBackButtonPressed() { - if (_accountListOverlay.IsVisible) + if (_accountListOverlay.IsVisible) { _accountListOverlay.HideAsync().FireAndForget(); return true; diff --git a/src/Core/Pages/Accounts/HomePageViewModel.cs b/src/Core/Pages/Accounts/HomePageViewModel.cs index 1c4751649..13686ee99 100644 --- a/src/Core/Pages/Accounts/HomePageViewModel.cs +++ b/src/Core/Pages/Accounts/HomePageViewModel.cs @@ -1,23 +1,17 @@ -using System; -using System.Threading.Tasks; -using Bit.App.Abstractions; +using Bit.App.Abstractions; using Bit.App.Controls; using Bit.Core.Resources.Localization; using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; -using Bit.Core.Models.Data; using Bit.Core.Utilities; - -using Microsoft.Maui.Controls; -using Microsoft.Maui; +using BwRegion = Bit.Core.Enums.Region; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { public class HomeViewModel : BaseViewModel { - private const string LOGGING_IN_ON_US = "bitwarden.com"; - private const string LOGGING_IN_ON_EU = "bitwarden.eu"; private readonly IStateService _stateService; private readonly IMessagingService _messagingService; @@ -50,12 +44,12 @@ namespace Bit.App.Pages AllowActiveAccountSelection = true }; RememberEmailCommand = new Command(() => RememberEmail = !RememberEmail); - ContinueCommand = new AsyncCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false); - CreateAccountCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction), + ContinueCommand = CreateDefaultAsyncRelayCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false); + CreateAccountCommand = CreateDefaultAsyncRelayCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction), onException: _logger.Exception, allowsMultipleExecutions: false); - CloseCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction), + CloseCommand = CreateDefaultAsyncRelayCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction), onException: _logger.Exception, allowsMultipleExecutions: false); - ShowEnvironmentPickerCommand = new AsyncCommand(ShowEnvironmentPickerAsync, + ShowEnvironmentPickerCommand = CreateDefaultAsyncRelayCommand(ShowEnvironmentPickerAsync, onException: _logger.Exception, allowsMultipleExecutions: false); InitAsync().FireAndForget(); } @@ -113,10 +107,10 @@ namespace Bit.App.Pages public Action StartEnvironmentAction { get; set; } public Action CloseAction { get; set; } public Command RememberEmailCommand { get; set; } - public AsyncCommand ContinueCommand { get; } - public AsyncCommand CloseCommand { get; } - public AsyncCommand CreateAccountCommand { get; } - public AsyncCommand ShowEnvironmentPickerCommand { get; } + public AsyncRelayCommand ContinueCommand { get; } + public AsyncRelayCommand CloseCommand { get; } + public AsyncRelayCommand CreateAccountCommand { get; } + public AsyncRelayCommand ShowEnvironmentPickerCommand { get; } public async Task InitAsync() { @@ -166,10 +160,10 @@ namespace Bit.App.Pages { _displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag); var options = _displayEuEnvironment - ? new string[] { LOGGING_IN_ON_US, LOGGING_IN_ON_EU, AppResources.SelfHosted } - : new string[] { LOGGING_IN_ON_US, AppResources.SelfHosted }; + ? new string[] { BwRegion.US.Domain(), BwRegion.EU.Domain(), AppResources.SelfHosted } + : new string[] { BwRegion.US.Domain(), AppResources.SelfHosted }; - await Device.InvokeOnMainThreadAsync(async () => + await MainThread.InvokeOnMainThreadAsync(async () => { var result = await Page.DisplayActionSheet(AppResources.LoggingInOn, AppResources.Cancel, null, options); @@ -184,35 +178,23 @@ namespace Bit.App.Pages return; } - await _environmentService.SetUrlsAsync(result == LOGGING_IN_ON_EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS); + await _environmentService.SetRegionAsync(result == BwRegion.EU.Domain() ? BwRegion.EU : BwRegion.US); await _configService.GetAsync(true); SelectedEnvironmentName = result; }); } - public async Task UpdateEnvironment() + public async Task UpdateEnvironmentAsync() { - var environmentsSaved = await _stateService.GetPreAuthEnvironmentUrlsAsync(); - if (environmentsSaved == null || environmentsSaved.IsEmpty) + var region = _environmentService.SelectedRegion; + if (region == BwRegion.SelfHosted) { - await _environmentService.SetUrlsAsync(EnvironmentUrlData.DefaultUS); - environmentsSaved = EnvironmentUrlData.DefaultUS; - SelectedEnvironmentName = LOGGING_IN_ON_US; - return; - } - - if (environmentsSaved.Base == EnvironmentUrlData.DefaultUS.Base) - { - SelectedEnvironmentName = LOGGING_IN_ON_US; - } - else if (environmentsSaved.Base == EnvironmentUrlData.DefaultEU.Base) - { - SelectedEnvironmentName = LOGGING_IN_ON_EU; + SelectedEnvironmentName = AppResources.SelfHosted; + await _configService.GetAsync(true); } else { - await _configService.GetAsync(true); - SelectedEnvironmentName = AppResources.SelfHosted; + SelectedEnvironmentName = region.Domain(); } } } diff --git a/src/Core/Pages/Accounts/LockPageViewModel.cs b/src/Core/Pages/Accounts/LockPageViewModel.cs index 0aa06a76c..2464524cf 100644 --- a/src/Core/Pages/Accounts/LockPageViewModel.cs +++ b/src/Core/Pages/Accounts/LockPageViewModel.cs @@ -208,13 +208,8 @@ namespace Bit.App.Pages _logger.Exception(new NullReferenceException("Email not found in storage")); return; } - var webVault = _environmentService.GetWebVaultUrl(true); - if (string.IsNullOrWhiteSpace(webVault)) - { - webVault = "https://bitwarden.com"; - } - var webVaultHostname = CoreHelpers.GetHostname(webVault); - LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname); + + LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, _environmentService.GetCurrentDomain()); if (PinEnabled) { PageTitle = AppResources.VerifyPIN; diff --git a/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs b/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs index ad2f56e2c..050cb80bc 100644 --- a/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs +++ b/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs @@ -55,19 +55,19 @@ namespace Bit.App.Pages PageTitle = AppResources.LogInInitiated; RememberThisDevice = true; - ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction), + ApproveWithMyOtherDeviceCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction), + RequestAdminApprovalCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction), + ApproveWithMasterPasswordCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync, + ContinueCommand = CreateDefaultAsyncRelayCommand(CreateNewSsoUserAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); diff --git a/src/Core/Pages/Accounts/LoginPageViewModel.cs b/src/Core/Pages/Accounts/LoginPageViewModel.cs index 214f3d96a..857cec4f5 100644 --- a/src/Core/Pages/Accounts/LoginPageViewModel.cs +++ b/src/Core/Pages/Accounts/LoginPageViewModel.cs @@ -62,8 +62,8 @@ namespace Bit.App.Pages PageTitle = AppResources.Bitwarden; TogglePasswordCommand = new Command(TogglePassword); LogInCommand = new Command(async () => await LogInAsync()); - MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); - LogInWithDeviceCommand = new AsyncCommand(() => Device.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false); + MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); + LogInWithDeviceCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false); AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { @@ -163,7 +163,7 @@ namespace Bit.App.Pages Email = await _stateService.GetRememberedEmailAsync(); } CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email; - EnvironmentDomainName = CoreHelpers.GetDomain((await _stateService.GetPreAuthEnvironmentUrlsAsync())?.Base); + EnvironmentDomainName = _environmentService.GetCurrentDomain(); IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, await _appIdService.GetAppIdAsync()); } catch (ApiException apiEx) when (apiEx.Error.StatusCode == System.Net.HttpStatusCode.Unauthorized) diff --git a/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs b/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs index ab96ebde1..0a28f76f8 100644 --- a/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs +++ b/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs @@ -56,11 +56,11 @@ namespace Bit.App.Pages _cryptoFunctionService = ServiceContainer.Resolve(); _cryptoService = ServiceContainer.Resolve(); - CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync, + CreatePasswordlessLoginCommand = CreateDefaultAsyncRelayCommand(CreatePasswordlessLoginAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - CloseCommand = new AsyncCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction), + CloseCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction), onException: _logger.Exception, allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs b/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs index 3ba5b87fb..a5809659c 100644 --- a/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs +++ b/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs @@ -39,10 +39,10 @@ namespace Bit.App.Pages PageTitle = AppResources.LogInRequested; - AcceptRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(true), + AcceptRequestCommand = CreateDefaultAsyncRelayCommand(() => PasswordlessLoginAsync(true), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - RejectRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(false), + RejectRequestCommand = CreateDefaultAsyncRelayCommand(() => PasswordlessLoginAsync(false), onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs b/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs index 975784b7c..d56bdc273 100644 --- a/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs @@ -54,7 +54,7 @@ namespace Bit.App.Pages _cryptoService = ServiceContainer.Resolve(); PageTitle = AppResources.Bitwarden; - LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false); + LogInCommand = CreateDefaultAsyncRelayCommand(LogInAsync, allowsMultipleExecutions: false); } public string OrgIdentifier @@ -231,19 +231,18 @@ namespace Bit.App.Pages StartDeviceApprovalOptionsAction?.Invoke(); return; } - // If user doesn't have a MP, but has reset password permission, they must set a MP - if (!decryptOptions.HasMasterPassword && - decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission) - { - StartSetPasswordAction?.Invoke(); - return; - } // Update temp password only if the device is trusted and therefore has a decrypted User Key set if (response.ForcePasswordReset) { UpdateTempPasswordAction?.Invoke(); return; } + // If user doesn't have a MP, but has reset password permission, they must set a MP + if (!decryptOptions.HasMasterPassword && + decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission) + { + await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission); + } // Device is trusted and has keys, so we can decrypt _syncService.FullSyncAsync(true).FireAndForget(); SsoAuthSuccessAction?.Invoke(); diff --git a/src/Core/Pages/Accounts/SetPasswordPage.xaml b/src/Core/Pages/Accounts/SetPasswordPage.xaml index 7ccb686e0..a03140973 100644 --- a/src/Core/Pages/Accounts/SetPasswordPage.xaml +++ b/src/Core/Pages/Accounts/SetPasswordPage.xaml @@ -29,7 +29,7 @@ - diff --git a/src/Core/Pages/Accounts/SetPasswordPageViewModel.cs b/src/Core/Pages/Accounts/SetPasswordPageViewModel.cs index 167671174..685c9dce6 100644 --- a/src/Core/Pages/Accounts/SetPasswordPageViewModel.cs +++ b/src/Core/Pages/Accounts/SetPasswordPageViewModel.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; -using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -13,9 +9,6 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; using Bit.Core.Utilities; -using Microsoft.Maui.Networking; -using Microsoft.Maui.Controls; -using Microsoft.Maui; namespace Bit.App.Pages { @@ -29,6 +22,7 @@ namespace Bit.App.Pages private readonly IPolicyService _policyService; private readonly IPasswordGenerationService _passwordGenerationService; private readonly II18nService _i18nService; + private readonly ISyncService _syncService; private bool _showPassword; private bool _isPolicyInEffect; @@ -47,6 +41,7 @@ namespace Bit.App.Pages _passwordGenerationService = ServiceContainer.Resolve("passwordGenerationService"); _i18nService = ServiceContainer.Resolve("i18nService"); + _syncService = ServiceContainer.Resolve(); PageTitle = AppResources.SetMasterPassword; TogglePasswordCommand = new Command(TogglePassword); @@ -101,11 +96,17 @@ namespace Bit.App.Pages public Action CloseAction { get; set; } public string OrgIdentifier { get; set; } public string OrgId { get; set; } + public ForcePasswordResetReason? ForceSetPasswordReason { get; private set; } + + public string SetMasterPasswordSummary => ForceSetPasswordReason == ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission + ? AppResources.YourOrganizationPermissionsWereUpdatedRequeringYouToSetAMasterPassword + : AppResources.YourOrganizationRequiresYouToSetAMasterPassword; public async Task InitAsync() { await CheckPasswordPolicy(); - + ForceSetPasswordReason = await _stateService.GetForcePasswordResetReasonAsync(); + TriggerPropertyChanged(nameof(SetMasterPasswordSummary)); try { var response = await _apiService.GetOrganizationAutoEnrollStatusAsync(OrgIdentifier); @@ -172,8 +173,7 @@ namespace Bit.App.Pages var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey, await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync()); - - var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey); + var keysRequest = await GetKeysForSetPasswordRequestAsync(newUserKey); var request = new SetPasswordRequest { MasterPasswordHash = masterPasswordHash, @@ -184,16 +184,12 @@ namespace Bit.App.Pages KdfMemory = kdfConfig.Memory, KdfParallelism = kdfConfig.Parallelism, OrgIdentifier = OrgIdentifier, - Keys = new KeysRequest - { - PublicKey = newPublicKey, - EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString - } + Keys = keysRequest }; try { - await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); + await _deviceActionService.ShowLoadingAsync(AppResources.Loading); // Set Password and relevant information await _apiService.SetPasswordAsync(request); await _stateService.SetKdfConfigurationAsync(kdfConfig); @@ -201,7 +197,13 @@ namespace Bit.App.Pages await _cryptoService.SetMasterKeyAsync(newMasterKey); await _cryptoService.SetMasterKeyHashAsync(localMasterPasswordHash); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString); - await _cryptoService.SetUserPrivateKeyAsync(newProtectedPrivateKey.EncryptedString); + + // Set private key only for new JIT provisioned users in MP encryption orgs + // Existing TDE users will have private key set on sync or on login + if (keysRequest != null) + { + await _cryptoService.SetUserPrivateKeyAsync(keysRequest.EncryptedPrivateKey); + } if (ResetPasswordAutoEnroll) { @@ -222,6 +224,9 @@ namespace Bit.App.Pages await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest); } + await _stateService.SetForcePasswordResetReasonAsync(null); + await _stateService.SetUserHasMasterPasswordAsync(true); + await _syncService.FullSyncAsync(true); await _deviceActionService.HideLoadingAsync(); SetPasswordSuccessAction?.Invoke(); } @@ -236,6 +241,21 @@ namespace Bit.App.Pages } } + private async Task GetKeysForSetPasswordRequestAsync(UserKey newUserKey) + { + if (ForceSetPasswordReason == ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission) + { + return null; + } + + var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey); + return new KeysRequest + { + PublicKey = newPublicKey, + EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString + }; + } + public void TogglePassword() { ShowPassword = !ShowPassword; diff --git a/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs b/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs index 5ac680d48..0952c5be4 100644 --- a/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs @@ -10,6 +10,7 @@ using Bit.App.Utilities; 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.Services; using Bit.Core.Utilities; @@ -62,7 +63,7 @@ namespace Bit.App.Pages PageTitle = AppResources.TwoStepLogin; SubmitCommand = new Command(async () => await SubmitAsync()); - MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); + MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); } public string TotpInstruction @@ -338,20 +339,18 @@ Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos : StartDeviceApprovalOptionsAction?.Invoke(); return; } - // If user doesn't have a MP, but has reset password permission, they must set a MP - if (!decryptOptions.HasMasterPassword && - decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission) - { - StartSetPasswordAction?.Invoke(); - return; - } // Update temp password only if the device is trusted and therefore has a decrypted User Key set if (result.ForcePasswordReset) { UpdateTempPasswordAction?.Invoke(); return; } - + // If user doesn't have a MP, but has reset password permission, they must set a MP + if (!decryptOptions.HasMasterPassword && + decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission) + { + await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission); + } // Device is trusted and has keys, so we can decrypt _syncService.FullSyncAsync(true).FireAndForget(); await TwoFactorAuthSuccessAsync(); diff --git a/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs b/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs index 78ddfb10c..df095c655 100644 --- a/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs +++ b/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs @@ -11,6 +11,7 @@ using Bit.Core.Utilities; using Microsoft.Maui.Controls; using Microsoft.Maui; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -25,14 +26,14 @@ namespace Bit.App.Pages PageTitle = AppResources.UpdateMasterPassword; TogglePasswordCommand = new Command(TogglePassword); ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); - SubmitCommand = new AsyncCommand(SubmitAsync, + SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); _userVerificationService = ServiceContainer.Resolve(); } - public AsyncCommand SubmitCommand { get; } + public AsyncRelayCommand SubmitCommand { get; } public Command TogglePasswordCommand { get; } public Command ToggleConfirmPasswordCommand { get; } public Action UpdateTempPasswordSuccessAction { get; set; } diff --git a/src/Core/Pages/Accounts/VerificationCodeViewModel.cs b/src/Core/Pages/Accounts/VerificationCodeViewModel.cs index eb6d63b72..bbd388f7f 100644 --- a/src/Core/Pages/Accounts/VerificationCodeViewModel.cs +++ b/src/Core/Pages/Accounts/VerificationCodeViewModel.cs @@ -39,8 +39,8 @@ namespace Bit.App.Pages PageTitle = AppResources.VerificationCode; TogglePasswordCommand = new Command(TogglePassword); - MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false); - RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false); + MainActionCommand = CreateDefaultAsyncRelayCommand(MainActionAsync, allowsMultipleExecutions: false); + RequestOTPCommand = CreateDefaultAsyncRelayCommand(RequestOTPAsync, allowsMultipleExecutions: false); } public bool ShowPassword diff --git a/src/Core/Pages/BaseViewModel.cs b/src/Core/Pages/BaseViewModel.cs index e0c95ab82..a19107e11 100644 --- a/src/Core/Pages/BaseViewModel.cs +++ b/src/Core/Pages/BaseViewModel.cs @@ -1,27 +1,13 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; -using Bit.App.Abstractions; -using Bit.App.Controls; +using Bit.App.Controls; using Bit.Core.Resources.Localization; -using Bit.Core.Abstractions; -using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Microsoft.Maui.Networking; -using Microsoft.Maui.Controls; -using Microsoft.Maui; -using Bit.App.Utilities; - namespace Bit.App.Pages { public abstract class BaseViewModel : ExtendedViewModel { private string _pageTitle = string.Empty; private AvatarImageSource _avatar; - private LazyResolve _deviceActionService = new LazyResolve(); - private LazyResolve _platformUtilsService = new LazyResolve(); - private LazyResolve _logger = new LazyResolve(); public string PageTitle { @@ -37,29 +23,6 @@ namespace Bit.App.Pages public ContentPage Page { get; set; } - protected void HandleException(Exception ex, string message = null) - { - if (ex is ApiException apiException && apiException.Error != null) - { - message = apiException.Error.GetSingleMessage(); - } - - Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () => - { - await _deviceActionService.Value.HideLoadingAsync(); - await _platformUtilsService.Value.ShowDialogAsync(message ?? AppResources.GenericErrorMessage); - }).FireAndForget(); - _logger.Value.Exception(ex); - } - - protected AsyncCommand CreateDefaultAsyncCommnad(Func execute, Func canExecute = null) - { - return new AsyncCommand(execute, - canExecute, - ex => HandleException(ex), - allowsMultipleExecutions: false); - } - protected async Task HasConnectivityAsync() { if (Connectivity.NetworkAccess == NetworkAccess.None) diff --git a/src/Core/Pages/Generator/GeneratorPageViewModel.cs b/src/Core/Pages/Generator/GeneratorPageViewModel.cs index d6226c207..50e9d2f30 100644 --- a/src/Core/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/Core/Pages/Generator/GeneratorPageViewModel.cs @@ -89,11 +89,11 @@ namespace Bit.App.Pages }; UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp); - RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); - RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); + RegenerateCommand = CreateDefaultAsyncRelayCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); + RegenerateUsernameCommand = CreateDefaultAsyncRelayCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret); - CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); - CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); + CopyCommand = CreateDefaultAsyncRelayCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); + CloseCommand = CreateDefaultAsyncRelayCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); } public List GeneratorTypeOptions { get; set; } diff --git a/src/Core/Pages/PickerViewModel.cs b/src/Core/Pages/PickerViewModel.cs index 43168ecdc..d94643b08 100644 --- a/src/Core/Pages/PickerViewModel.cs +++ b/src/Core/Pages/PickerViewModel.cs @@ -2,8 +2,7 @@ using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Utilities; - -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -33,10 +32,10 @@ namespace Bit.App.Pages _onSelectionChangingAsync = onSelectionChangingAsync; _title = title; - SelectOptionCommand = new AsyncCommand(SelectOptionAsync, canExecuteSelectOptionCommand, onSelectOptionCommandException, allowsMultipleExecutions: false); + SelectOptionCommand = CreateDefaultAsyncRelayCommand(SelectOptionAsync, canExecuteSelectOptionCommand, onSelectOptionCommandException, allowsMultipleExecutions: false); } - public AsyncCommand SelectOptionCommand { get; } + public AsyncRelayCommand SelectOptionCommand { get; } public TKey SelectedKey => _selectedKey; diff --git a/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs b/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs index 7b1e69a08..8a69ae295 100644 --- a/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs @@ -1,13 +1,10 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -29,32 +26,32 @@ namespace Bit.App.Pages var environmentService = ServiceContainer.Resolve(); var clipboardService = ServiceContainer.Resolve(); - ToggleSubmitCrashLogsCommand = CreateDefaultAsyncCommnad(ToggleSubmitCrashLogsAsync); + ToggleSubmitCrashLogsCommand = CreateDefaultAsyncRelayCommand(ToggleSubmitCrashLogsAsync, allowsMultipleExecutions: false); - GoToHelpCenterCommand = CreateDefaultAsyncCommnad( + GoToHelpCenterCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.LearnMoreAboutHowToUseBitwardenOnTheHelpCenter, AppResources.ContinueToHelpCenter, - ExternalLinksConstants.HELP_CENTER)); + ExternalLinksConstants.HELP_CENTER), allowsMultipleExecutions: false); - ContactBitwardenSupportCommand = CreateDefaultAsyncCommnad( + ContactBitwardenSupportCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.ContactSupportDescriptionLong, AppResources.ContinueToContactSupport, - ExternalLinksConstants.CONTACT_SUPPORT)); + ExternalLinksConstants.CONTACT_SUPPORT), allowsMultipleExecutions: false); - GoToWebVaultCommand = CreateDefaultAsyncCommnad( + GoToWebVaultCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp, AppResources.ContinueToWebApp, - environmentService.GetWebVaultUrl())); + environmentService.GetWebVaultUrl()), allowsMultipleExecutions: false); - GoToLearnAboutOrgsCommand = CreateDefaultAsyncCommnad( + GoToLearnAboutOrgsCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.LearnAboutOrganizationsDescriptionLong, string.Format(AppResources.ContinueToX, ExternalLinksConstants.BITWARDEN_WEBSITE), - ExternalLinksConstants.HELP_ABOUT_ORGANIZATIONS)); + ExternalLinksConstants.HELP_ABOUT_ORGANIZATIONS), allowsMultipleExecutions: false); - RateTheAppCommand = CreateDefaultAsyncCommnad(RateAppAsync); + RateTheAppCommand = CreateDefaultAsyncRelayCommand(RateAppAsync, allowsMultipleExecutions: false); - CopyAppInfoCommand = CreateDefaultAsyncCommnad( - () => clipboardService.CopyTextAsync(AppInfo)); + CopyAppInfoCommand = CreateDefaultAsyncRelayCommand( + () => clipboardService.CopyTextAsync(AppInfo), allowsMultipleExecutions: false); } public bool ShouldSubmitCrashLogs @@ -80,7 +77,7 @@ namespace Bit.App.Pages } } - public AsyncCommand ToggleSubmitCrashLogsCommand { get; } + public AsyncRelayCommand ToggleSubmitCrashLogsCommand { get; } public ICommand GoToHelpCenterCommand { get; } public ICommand ContactBitwardenSupportCommand { get; } public ICommand GoToWebVaultCommand { get; } @@ -97,7 +94,7 @@ namespace Bit.App.Pages MainThread.BeginInvokeOnMainThread(() => { TriggerPropertyChanged(nameof(ShouldSubmitCrashLogs)); - ToggleSubmitCrashLogsCommand.RaiseCanExecuteChanged(); + ToggleSubmitCrashLogsCommand.NotifyCanExecuteChanged(); }); } diff --git a/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs b/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs index ef4792e40..6f91c56e4 100644 --- a/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -64,7 +58,7 @@ namespace Bit.App.Pages () => _inited, ex => HandleException(ex)); - ToggleShowWebsiteIconsCommand = CreateDefaultAsyncCommnad(ToggleShowWebsiteIconsAsync, () => _inited); + ToggleShowWebsiteIconsCommand = CreateDefaultAsyncRelayCommand(ToggleShowWebsiteIconsAsync, () => _inited, allowsMultipleExecutions: false); } public PickerViewModel LanguagePickerViewModel { get; } @@ -87,7 +81,7 @@ namespace Bit.App.Pages public bool IsShowWebsiteIconsEnabled => ToggleShowWebsiteIconsCommand.CanExecute(null); - public AsyncCommand ToggleShowWebsiteIconsCommand { get; } + public AsyncRelayCommand ToggleShowWebsiteIconsCommand { get; } public async Task InitAsync() { @@ -102,10 +96,10 @@ namespace Bit.App.Pages MainThread.BeginInvokeOnMainThread(() => { - ToggleShowWebsiteIconsCommand.RaiseCanExecuteChanged(); - LanguagePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - ThemePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - DefaultDarkThemePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + ToggleShowWebsiteIconsCommand.NotifyCanExecuteChanged(); + LanguagePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + ThemePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + DefaultDarkThemePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); }); } diff --git a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs index 45d499a24..90e3b42c3 100644 --- a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs +++ b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs @@ -1,10 +1,6 @@ -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.Core.Resources.Localization; -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -16,8 +12,7 @@ namespace Bit.App.Pages private bool _useDrawOver; private bool _askToAddLogin; - public bool SupportsAndroidAutofillServices => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes -Device.RuntimePlatform == Device.Android && _deviceActionService.SupportsAutofillServices(); + public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices(); public bool UseAutofillServices { @@ -45,8 +40,7 @@ Device.RuntimePlatform == Device.Android && _deviceActionService.SupportsAutofil } } - public bool ShowUseAccessibilityToggle => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes -Device.RuntimePlatform == Device.Android; + public bool ShowUseAccessibilityToggle => DeviceInfo.Platform == DevicePlatform.Android; public string UseAccessibilityDescription => _deviceActionService.GetAutofillAccessibilityDescription(); @@ -90,21 +84,21 @@ Device.RuntimePlatform == Device.Android; } } - public AsyncCommand ToggleUseAutofillServicesCommand { get; private set; } - public AsyncCommand ToggleUseInlineAutofillCommand { get; private set; } - public AsyncCommand ToggleUseAccessibilityCommand { get; private set; } - public AsyncCommand ToggleUseDrawOverCommand { get; private set; } - public AsyncCommand ToggleAskToAddLoginCommand { get; private set; } + public AsyncRelayCommand ToggleUseAutofillServicesCommand { get; private set; } + public AsyncRelayCommand ToggleUseInlineAutofillCommand { get; private set; } + public AsyncRelayCommand ToggleUseAccessibilityCommand { get; private set; } + public AsyncRelayCommand ToggleUseDrawOverCommand { get; private set; } + public AsyncRelayCommand ToggleAskToAddLoginCommand { get; private set; } public ICommand GoToBlockAutofillUrisCommand { get; private set; } private void InitAndroidCommands() { - ToggleUseAutofillServicesCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited); - ToggleUseInlineAutofillCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited); - ToggleUseAccessibilityCommand = CreateDefaultAsyncCommnad(ToggleUseAccessibilityAsync, () => _inited); - ToggleUseDrawOverCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited); - ToggleAskToAddLoginCommand = CreateDefaultAsyncCommnad(ToggleAskToAddLoginAsync, () => _inited); - GoToBlockAutofillUrisCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage())); + ToggleUseAutofillServicesCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited, allowsMultipleExecutions: false); + ToggleUseInlineAutofillCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited, allowsMultipleExecutions: false); + ToggleUseAccessibilityCommand = CreateDefaultAsyncRelayCommand(ToggleUseAccessibilityAsync, () => _inited, allowsMultipleExecutions: false); + ToggleUseDrawOverCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited, allowsMultipleExecutions: false); + ToggleAskToAddLoginCommand = CreateDefaultAsyncRelayCommand(ToggleAskToAddLoginAsync, () => _inited, allowsMultipleExecutions: false); + GoToBlockAutofillUrisCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()), allowsMultipleExecutions: false); } private async Task InitAndroidAutofillSettingsAsync() diff --git a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs index 58e7ee292..8c247b0fe 100644 --- a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -36,7 +33,7 @@ namespace Bit.App.Pages () => _inited, ex => HandleException(ex)); - ToggleCopyTotpAutomaticallyCommand = CreateDefaultAsyncCommnad(ToggleCopyTotpAutomaticallyAsync, () => _inited); + ToggleCopyTotpAutomaticallyCommand = CreateDefaultAsyncRelayCommand(ToggleCopyTotpAutomaticallyAsync, () => _inited, allowsMultipleExecutions: false); InitAndroidCommands(); InitIOSCommands(); @@ -56,7 +53,7 @@ namespace Bit.App.Pages public PickerViewModel DefaultUriMatchDetectionPickerViewModel { get; } - public AsyncCommand ToggleCopyTotpAutomaticallyCommand { get; private set; } + public AsyncRelayCommand ToggleCopyTotpAutomaticallyCommand { get; private set; } public async Task InitAsync() { @@ -72,11 +69,11 @@ namespace Bit.App.Pages { TriggerPropertyChanged(nameof(CopyTotpAutomatically)); - ToggleUseAutofillServicesCommand.RaiseCanExecuteChanged(); - ToggleUseInlineAutofillCommand.RaiseCanExecuteChanged(); - ToggleUseAccessibilityCommand.RaiseCanExecuteChanged(); - ToggleUseDrawOverCommand.RaiseCanExecuteChanged(); - DefaultUriMatchDetectionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + ToggleUseAutofillServicesCommand.NotifyCanExecuteChanged(); + ToggleUseInlineAutofillCommand.NotifyCanExecuteChanged(); + ToggleUseAccessibilityCommand.NotifyCanExecuteChanged(); + ToggleUseDrawOverCommand.NotifyCanExecuteChanged(); + DefaultUriMatchDetectionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); }); } diff --git a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs index 954bf3b2d..a2f2d1101 100644 --- a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs +++ b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs @@ -1,21 +1,18 @@ using System.Windows.Input; -using Microsoft.Maui.Controls; -using Microsoft.Maui; namespace Bit.App.Pages { public partial class AutofillSettingsPageViewModel { - public bool SupportsiOSAutofill => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes -Device.RuntimePlatform == Device.iOS && _deviceActionService.SupportsAutofillServices(); + public bool SupportsiOSAutofill => DeviceInfo.Platform == DevicePlatform.iOS && _deviceActionService.SupportsAutofillServices(); public ICommand GoToPasswordAutofillCommand { get; private set; } public ICommand GoToAppExtensionCommand { get; private set; } private void InitIOSCommands() { - GoToPasswordAutofillCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage()))); - GoToAppExtensionCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()))); + GoToPasswordAutofillCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage())), allowsMultipleExecutions: false); + GoToAppExtensionCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage())), allowsMultipleExecutions: false); } } } diff --git a/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs b/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs index 510eb8629..05b85a794 100644 --- a/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs +++ b/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs @@ -28,11 +28,11 @@ namespace Bit.App.Pages _stateService = ServiceContainer.Resolve(); _deviceActionService = ServiceContainer.Resolve(); - AddUriCommand = new AsyncCommand(AddUriAsync, + AddUriCommand = CreateDefaultAsyncRelayCommand(AddUriAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - EditUriCommand = new AsyncCommand(EditUriAsync, + EditUriCommand = CreateDefaultAsyncRelayCommand(EditUriAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs b/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs index 64d299faa..083f8c4e6 100644 --- a/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs +++ b/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs @@ -13,6 +13,7 @@ using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Controls; using Microsoft.Maui; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -34,11 +35,11 @@ namespace Bit.App.Pages PageTitle = AppResources.PendingLogInRequests; LoginRequests = new ObservableRangeCollection(); - AnswerRequestCommand = new AsyncCommand(PasswordlessLoginAsync, + AnswerRequestCommand = CreateDefaultAsyncRelayCommand(PasswordlessLoginAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - DeclineAllRequestsCommand = new AsyncCommand(DeclineAllRequestsAsync, + DeclineAllRequestsCommand = CreateDefaultAsyncRelayCommand(DeclineAllRequestsAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); @@ -47,9 +48,9 @@ namespace Bit.App.Pages public ICommand RefreshCommand { get; } - public AsyncCommand AnswerRequestCommand { get; } + public AsyncRelayCommand AnswerRequestCommand { get; } - public AsyncCommand DeclineAllRequestsCommand { get; } + public AsyncRelayCommand DeclineAllRequestsCommand { get; } public ObservableRangeCollection LoginRequests { get; } diff --git a/src/Core/Pages/Settings/OtherSettingsPage.xaml b/src/Core/Pages/Settings/OtherSettingsPage.xaml index 99909b799..b158b63db 100644 --- a/src/Core/Pages/Settings/OtherSettingsPage.xaml +++ b/src/Core/Pages/Settings/OtherSettingsPage.xaml @@ -53,6 +53,7 @@ Title="{u:I18n AllowScreenCapture}" IsToggled="{Binding IsScreenCaptureAllowed}" IsEnabled="{Binding CanToggleeScreenCaptureAllowed}" + IsVisible="{OnPlatform Android=True, iOS=False}" AutomationId="AllowScreenCaptureSwitch" StyleClass="settings-item-view" HorizontalOptions="FillAndExpand" /> diff --git a/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs b/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs index 2be3b31b0..01a5443fc 100644 --- a/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs @@ -1,16 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; -using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; +using Bit.Core.Resources.Localization; using Bit.Core.Utilities; - -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; -using Bit.App.Utilities; using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages @@ -43,19 +35,21 @@ namespace Bit.App.Pages _watchDeviceService = ServiceContainer.Resolve(); _logger = ServiceContainer.Resolve(); - SyncCommand = CreateDefaultAsyncCommnad(SyncAsync, () => _inited); - ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncCommnad(ToggleIsScreenCaptureAllowedAsync, () => _inited); - ToggleShouldConnectToWatchCommand = CreateDefaultAsyncCommnad(ToggleShouldConnectToWatchAsync, () => _inited); + SyncCommand = CreateDefaultAsyncRelayCommand(SyncAsync, CanExecuteIfInited, allowsMultipleExecutions: false); + ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncRelayCommand(ToggleIsScreenCaptureAllowedAsync, CanExecuteIfInited, allowsMultipleExecutions: false); + ToggleShouldConnectToWatchCommand = CreateDefaultAsyncRelayCommand(ToggleShouldConnectToWatchAsync, CanExecuteIfInited, allowsMultipleExecutions: false); ClearClipboardPickerViewModel = new PickerViewModel( _deviceActionService, _logger, OnClearClipboardChangingAsync, AppResources.ClearClipboard, - () => _inited, + CanExecuteIfInited, ex => HandleException(ex)); } + private bool CanExecuteIfInited() => _inited; + public bool EnableSyncOnRefresh { get => _syncOnRefresh; @@ -104,9 +98,9 @@ namespace Bit.App.Pages public bool CanToggleShouldConnectToWatch => ToggleShouldConnectToWatchCommand.CanExecute(null); - public AsyncCommand SyncCommand { get; } - public AsyncCommand ToggleIsScreenCaptureAllowedCommand { get; } - public AsyncCommand ToggleShouldConnectToWatchCommand { get; } + public AsyncRelayCommand SyncCommand { get; } + public AsyncRelayCommand ToggleIsScreenCaptureAllowedCommand { get; } + public AsyncRelayCommand ToggleShouldConnectToWatchCommand { get; } public async Task InitAsync() { @@ -125,10 +119,10 @@ namespace Bit.App.Pages { TriggerPropertyChanged(nameof(IsScreenCaptureAllowed)); TriggerPropertyChanged(nameof(ShouldConnectToWatch)); - SyncCommand.RaiseCanExecuteChanged(); - ClearClipboardPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - ToggleIsScreenCaptureAllowedCommand.RaiseCanExecuteChanged(); - ToggleShouldConnectToWatchCommand.RaiseCanExecuteChanged(); + SyncCommand.NotifyCanExecuteChanged(); + ClearClipboardPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + ToggleIsScreenCaptureAllowedCommand.NotifyCanExecuteChanged(); + ToggleShouldConnectToWatchCommand.NotifyCanExecuteChanged(); }); } @@ -142,8 +136,7 @@ namespace Bit.App.Pages [30] = AppResources.ThirtySeconds, [60] = AppResources.OneMinute }; - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform != Device.iOS) + if (DeviceInfo.Platform != DevicePlatform.iOS) { clearClipboardOptions.Add(120, AppResources.TwoMinutes); clearClipboardOptions.Add(300, AppResources.FiveMinutes); diff --git a/src/Core/Pages/Settings/SecuritySettingsPage.xaml b/src/Core/Pages/Settings/SecuritySettingsPage.xaml index 3d0147cf4..25f1612db 100644 --- a/src/Core/Pages/Settings/SecuritySettingsPage.xaml +++ b/src/Core/Pages/Settings/SecuritySettingsPage.xaml @@ -67,7 +67,7 @@ - + @@ -147,6 +149,7 @@ diff --git a/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs b/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs index af81910fb..bc247295b 100644 --- a/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Pages.Accounts; using Bit.Core.Resources.Localization; @@ -12,10 +8,7 @@ using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Utilities; - -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -77,19 +70,19 @@ namespace Bit.App.Pages _logger, OnVaultTimeoutActionChangingAsync, AppResources.SessionTimeoutAction, - () => _inited && !HasVaultTimeoutActionPolicy, + () => _inited && !HasVaultTimeoutActionPolicy && IsVaultTimeoutActionLockAllowed, ex => HandleException(ex)); - ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncCommnad(ToggleUseThisDeviceToApproveLoginRequestsAsync, () => _inited); - GoToPendingLogInRequestsCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage()))); - ToggleCanUnlockWithBiometricsCommand = CreateDefaultAsyncCommnad(ToggleCanUnlockWithBiometricsAsync, () => _inited); - ToggleCanUnlockWithPinCommand = CreateDefaultAsyncCommnad(ToggleCanUnlockWithPinAsync, () => _inited); - ShowAccountFingerprintPhraseCommand = CreateDefaultAsyncCommnad(ShowAccountFingerprintPhraseAsync); - GoToTwoStepLoginCommand = CreateDefaultAsyncCommnad(() => GoToWebVaultSettingsAsync(AppResources.TwoStepLoginDescriptionLong, AppResources.ContinueToWebApp)); - GoToChangeMasterPasswordCommand = CreateDefaultAsyncCommnad(() => GoToWebVaultSettingsAsync(AppResources.ChangeMasterPasswordDescriptionLong, AppResources.ContinueToWebApp)); - LockCommand = CreateDefaultAsyncCommnad(() => _vaultTimeoutService.LockAsync(true, true)); - LogOutCommand = CreateDefaultAsyncCommnad(LogOutAsync); - DeleteAccountCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()))); + ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncRelayCommand(ToggleUseThisDeviceToApproveLoginRequestsAsync, () => _inited, allowsMultipleExecutions: false); + GoToPendingLogInRequestsCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage())), allowsMultipleExecutions: false); + ToggleCanUnlockWithBiometricsCommand = CreateDefaultAsyncRelayCommand(ToggleCanUnlockWithBiometricsAsync, () => _inited, allowsMultipleExecutions: false); + ToggleCanUnlockWithPinCommand = CreateDefaultAsyncRelayCommand(ToggleCanUnlockWithPinAsync, () => _inited, allowsMultipleExecutions: false); + ShowAccountFingerprintPhraseCommand = CreateDefaultAsyncRelayCommand(ShowAccountFingerprintPhraseAsync, allowsMultipleExecutions: false); + GoToTwoStepLoginCommand = CreateDefaultAsyncRelayCommand(() => GoToWebVaultSettingsAsync(AppResources.TwoStepLoginDescriptionLong, AppResources.ContinueToWebApp), allowsMultipleExecutions: false); + GoToChangeMasterPasswordCommand = CreateDefaultAsyncRelayCommand(() => GoToWebVaultSettingsAsync(AppResources.ChangeMasterPasswordDescriptionLong, AppResources.ContinueToWebApp), allowsMultipleExecutions: false); + LockCommand = CreateDefaultAsyncRelayCommand(() => _vaultTimeoutService.LockAsync(true, true), allowsMultipleExecutions: false); + LogOutCommand = CreateDefaultAsyncRelayCommand(LogOutAsync, allowsMultipleExecutions: false); + DeleteAccountCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage())), allowsMultipleExecutions: false); } public bool UseThisDeviceToApproveLoginRequests @@ -114,8 +107,7 @@ namespace Bit.App.Pages } var biometricName = AppResources.Biometrics; - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform == Device.iOS) + if (DeviceInfo.Platform == DevicePlatform.iOS) { biometricName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID @@ -131,6 +123,7 @@ namespace Bit.App.Pages get => _canUnlockWithBiometrics; set { + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); if (SetProperty(ref _canUnlockWithBiometrics, value)) { ((ICommand)ToggleCanUnlockWithBiometricsCommand).Execute(null); @@ -143,6 +136,7 @@ namespace Bit.App.Pages get => _canUnlockWithPin; set { + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); if (SetProperty(ref _canUnlockWithPin, value)) { ((ICommand)ToggleCanUnlockWithPinCommand).Execute(null); @@ -150,6 +144,10 @@ namespace Bit.App.Pages } } + public bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _canUnlockWithBiometrics || _canUnlockWithPin; + + public string SetUpUnlockMethodLabel => IsVaultTimeoutActionLockAllowed ? null : AppResources.SetUpAnUnlockOptionToChangeYourVaultTimeoutAction; + public TimeSpan? CustomVaultTimeoutTime { get => _customVaultTimeoutTime; @@ -166,6 +164,7 @@ namespace Bit.App.Pages MainThread.BeginInvokeOnMainThread(() => SetProperty(ref _customVaultTimeoutTime, oldValue)); }); } + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); } } @@ -205,22 +204,19 @@ namespace Bit.App.Pages public bool ShowChangeMasterPassword { get; private set; } - private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _canUnlockWithBiometrics || _canUnlockWithPin; - private int? CurrentVaultTimeout => GetRawVaultTimeoutFrom(VaultTimeoutPickerViewModel.SelectedKey); - private bool IncludeLinksWithSubscriptionInfo => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes -Device.RuntimePlatform != Device.iOS; + private bool IncludeLinksWithSubscriptionInfo => DeviceInfo.Platform != DevicePlatform.iOS; private bool HasVaultTimeoutActionPolicy => !string.IsNullOrEmpty(_vaultTimeoutActionPolicy); public PickerViewModel VaultTimeoutPickerViewModel { get; } public PickerViewModel VaultTimeoutActionPickerViewModel { get; } - public AsyncCommand ToggleUseThisDeviceToApproveLoginRequestsCommand { get; } + public AsyncRelayCommand ToggleUseThisDeviceToApproveLoginRequestsCommand { get; } public ICommand GoToPendingLogInRequestsCommand { get; } - public AsyncCommand ToggleCanUnlockWithBiometricsCommand { get; } - public AsyncCommand ToggleCanUnlockWithPinCommand { get; } + public AsyncRelayCommand ToggleCanUnlockWithBiometricsCommand { get; } + public AsyncRelayCommand ToggleCanUnlockWithPinCommand { get; } public ICommand ShowAccountFingerprintPhraseCommand { get; } public ICommand GoToTwoStepLoginCommand { get; } public ICommand GoToChangeMasterPasswordCommand { get; } @@ -256,11 +252,12 @@ Device.RuntimePlatform != Device.iOS; TriggerPropertyChanged(nameof(VaultTimeoutPolicyDescription)); TriggerPropertyChanged(nameof(ShowChangeMasterPassword)); TriggerUpdateCustomVaultTimeoutPicker(); - ToggleUseThisDeviceToApproveLoginRequestsCommand.RaiseCanExecuteChanged(); - ToggleCanUnlockWithBiometricsCommand.RaiseCanExecuteChanged(); - ToggleCanUnlockWithPinCommand.RaiseCanExecuteChanged(); - VaultTimeoutPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); + ToggleUseThisDeviceToApproveLoginRequestsCommand.NotifyCanExecuteChanged(); + ToggleCanUnlockWithBiometricsCommand.NotifyCanExecuteChanged(); + ToggleCanUnlockWithPinCommand.NotifyCanExecuteChanged(); + VaultTimeoutPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); }); } @@ -275,7 +272,7 @@ Device.RuntimePlatform != Device.iOS; _maximumVaultTimeoutPolicy = maximumVaultTimeoutPolicy?.GetInt(Policy.MINUTES_KEY); _vaultTimeoutActionPolicy = maximumVaultTimeoutPolicy?.GetString(Policy.ACTION_KEY); - MainThread.BeginInvokeOnMainThread(VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged); + MainThread.BeginInvokeOnMainThread(VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged); } private async Task InitVaultTimeoutPickerAsync() @@ -308,6 +305,7 @@ Device.RuntimePlatform != Device.iOS; { _customVaultTimeoutTime = TimeSpan.FromMinutes(vaultTimeout); } + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); } private async Task InitVaultTimeoutActionPickerAsync() @@ -327,6 +325,7 @@ Device.RuntimePlatform != Device.iOS; } VaultTimeoutActionPickerViewModel.Init(options, timeoutAction, IsVaultTimeoutActionLockAllowed ? VaultTimeoutAction.Lock : VaultTimeoutAction.Logout); + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); } private async Task ToggleUseThisDeviceToApproveLoginRequestsAsync() @@ -363,15 +362,15 @@ Device.RuntimePlatform != Device.iOS; { if (!_canUnlockWithBiometrics) { + MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics))); await UpdateVaultTimeoutActionIfNeededAsync(); await _biometricsService.SetCanUnlockWithBiometricsAsync(CanUnlockWithBiometrics); return; } - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (!_supportsBiometric || - !await _platformUtilsService.AuthenticateBiometricAsync(null, Device.RuntimePlatform == Device.Android ? "." : null)) + !await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null)) { _canUnlockWithBiometrics = false; MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics))); @@ -379,11 +378,12 @@ Device.RuntimePlatform != Device.iOS; } await _biometricsService.SetCanUnlockWithBiometricsAsync(CanUnlockWithBiometrics); + await InitVaultTimeoutActionPickerAsync(); } public async Task ToggleCanUnlockWithPinAsync() { - if (!CanUnlockWithPin) + if (!_canUnlockWithPin) { await _vaultTimeoutService.ClearAsync(); await UpdateVaultTimeoutActionIfNeededAsync(); @@ -407,10 +407,12 @@ Device.RuntimePlatform != Device.iOS; AppResources.No); await _userPinService.SetupPinAsync(newPin, requireMasterPasswordOnRestart); + await InitVaultTimeoutActionPickerAsync(); } private async Task UpdateVaultTimeoutActionIfNeededAsync() { + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); if (IsVaultTimeoutActionLockAllowed) { return; @@ -471,6 +473,16 @@ Device.RuntimePlatform != Device.iOS; TriggerPropertyChanged(nameof(CustomVaultTimeoutTime)); } + private void TriggerVaultTimeoutActionLockAllowedPropertyChanged() + { + MainThread.BeginInvokeOnMainThread(() => + { + TriggerPropertyChanged(nameof(IsVaultTimeoutActionLockAllowed)); + TriggerPropertyChanged(nameof(SetUpUnlockMethodLabel)); + VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + }); + } + private int? GetRawVaultTimeoutFrom(int vaultTimeoutPickerKey) { if (vaultTimeoutPickerKey == NEVER_SESSION_TIMEOUT_VALUE) @@ -505,7 +517,7 @@ Device.RuntimePlatform != Device.iOS; await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(CurrentVaultTimeout, timeoutActionKey); _messagingService.Send(AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND); - + TriggerVaultTimeoutActionLockAllowedPropertyChanged(); return true; } diff --git a/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index d20c6ab24..21f50f468 100644 --- a/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -1,5 +1,6 @@ using Bit.App.Utilities; using Bit.Core.Resources.Localization; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -7,7 +8,7 @@ namespace Bit.App.Pages { public SettingsPageViewModel() { - ExecuteSettingItemCommand = new AsyncCommand(item => item.ExecuteAsync(), + ExecuteSettingItemCommand = CreateDefaultAsyncRelayCommand(item => item.ExecuteAsync(), onException: ex => HandleException(ex), allowsMultipleExecutions: false); @@ -24,7 +25,7 @@ namespace Bit.App.Pages public List SettingsItems { get; } - public AsyncCommand ExecuteSettingItemCommand { get; } + public AsyncRelayCommand ExecuteSettingItemCommand { get; } private async Task NavigateToAsync(Page page) { diff --git a/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs b/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs index 3911fd6dd..ae676ba0b 100644 --- a/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs @@ -22,15 +22,15 @@ namespace Bit.App.Pages _platformUtilsService = ServiceContainer.Resolve(); _environmentService = ServiceContainer.Resolve(); - GoToFoldersCommand = new AsyncCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage())), + GoToFoldersCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage())), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - GoToExportVaultCommand = new AsyncCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage())), + GoToExportVaultCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage())), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - GoToImportItemsCommand = new AsyncCommand(GoToImportItemsAsync, + GoToImportItemsCommand = CreateDefaultAsyncRelayCommand(GoToImportItemsAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/TabsPage.cs b/src/Core/Pages/TabsPage.cs index 0a95a650c..6c2c64ec2 100644 --- a/src/Core/Pages/TabsPage.cs +++ b/src/Core/Pages/TabsPage.cs @@ -1,10 +1,14 @@ -using Bit.App.Effects; +using System; +using System.Linq; +using System.Threading.Tasks; +using Bit.App.Effects; using Bit.App.Models; using Bit.Core.Resources.Localization; using Bit.App.Utilities; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; using Bit.Core.Utilities; namespace Bit.App.Pages @@ -97,11 +101,38 @@ namespace Bit.App.Pages _messagingService.Send("convertAccountToKeyConnector"); } - var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync(); + await ForcePasswordResetIfNeededAsync(); + } - if (forcePasswordResetReason.HasValue) + private async Task ForcePasswordResetIfNeededAsync() + { + var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync(); + switch (forcePasswordResetReason) { - _messagingService.Send(Constants.ForceUpdatePassword); + case ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission: + // TDE users should only have one org + var userOrgs = await _stateService.GetOrganizationsAsync(); + if (userOrgs != null && userOrgs.Any()) + { + _messagingService.Send(Constants.ForceSetPassword, userOrgs.First().Value.Identifier); + return; + } + _logger.Value.Error("TDE user needs to set password but has no organizations."); + + var rememberedOrg = _stateService.GetRememberedOrgIdentifierAsync(); + if (rememberedOrg == null) + { + _logger.Value.Error("TDE user needs to set password but has no organizations or remembered org identifier."); + return; + } + _messagingService.Send(Constants.ForceSetPassword, rememberedOrg); + return; + case ForcePasswordResetReason.AdminForcePasswordReset: + case ForcePasswordResetReason.WeakMasterPasswordOnLogin: + _messagingService.Send(Constants.ForceUpdatePassword); + break; + default: + return; } } diff --git a/src/Core/Pages/Vault/AttachmentsPageViewModel.cs b/src/Core/Pages/Vault/AttachmentsPageViewModel.cs index 6e66b2f76..9446c757d 100644 --- a/src/Core/Pages/Vault/AttachmentsPageViewModel.cs +++ b/src/Core/Pages/Vault/AttachmentsPageViewModel.cs @@ -45,7 +45,7 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve(); Attachments = new ExtendedObservableCollection(); DeleteAttachmentCommand = new Command(DeleteAsync); - SubmitAsyncCommand = new AsyncCommand(SubmitAsync, allowsMultipleExecutions: false); + SubmitAsyncCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, allowsMultipleExecutions: false); PageTitle = AppResources.Attachments; } diff --git a/src/Core/Pages/Vault/BaseCipherViewModel.cs b/src/Core/Pages/Vault/BaseCipherViewModel.cs index 671aca2a5..8f7e084a4 100644 --- a/src/Core/Pages/Vault/BaseCipherViewModel.cs +++ b/src/Core/Pages/Vault/BaseCipherViewModel.cs @@ -7,6 +7,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -28,7 +29,7 @@ namespace Bit.App.Pages _auditService = ServiceContainer.Resolve("auditService"); _logger = ServiceContainer.Resolve("logger"); - CheckPasswordCommand = new AsyncCommand(CheckPasswordAsync, allowsMultipleExecutions: false); + CheckPasswordCommand = CreateDefaultAsyncRelayCommand(CheckPasswordAsync, allowsMultipleExecutions: false); } public CipherView Cipher @@ -39,7 +40,7 @@ namespace Bit.App.Pages public string CreationDate => string.Format(AppResources.CreatedXY, Cipher?.CreationDate.ToShortDateString(), Cipher?.CreationDate.ToShortTimeString()); - public AsyncCommand CheckPasswordCommand { get; } + public AsyncRelayCommand CheckPasswordCommand { get; } protected async Task CheckPasswordAsync() { diff --git a/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs b/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs index b44a26e15..abd2a6c49 100644 --- a/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs +++ b/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs @@ -16,6 +16,7 @@ using Bit.Core.Utilities; using Microsoft.Maui.Controls; using Microsoft.Maui; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; #nullable enable @@ -99,8 +100,8 @@ namespace Bit.App.Pages UriOptionsCommand = new Command(UriOptions); FieldOptionsCommand = new Command(FieldOptions); PasswordPromptHelpCommand = new Command(PasswordPromptHelp); - CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - GenerateUsernameCommand = new AsyncCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false); + CopyCommand = CreateDefaultAsyncRelayCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + GenerateUsernameCommand = CreateDefaultAsyncRelayCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false); Uris = new ExtendedObservableCollection(); Fields = new ExtendedObservableCollection(); Collections = new ExtendedObservableCollection(); @@ -163,8 +164,8 @@ namespace Bit.App.Pages public Command UriOptionsCommand { get; set; } public Command FieldOptionsCommand { get; set; } public Command PasswordPromptHelpCommand { get; set; } - public AsyncCommand CopyCommand { get; set; } - public AsyncCommand GenerateUsernameCommand { get; set; } + public AsyncRelayCommand CopyCommand { get; set; } + public AsyncRelayCommand GenerateUsernameCommand { get; set; } public string CipherId { get; set; } public string OrganizationId { get; set; } public string FolderId { get; set; } diff --git a/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs b/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs index fb56ee526..2ccb788ff 100644 --- a/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs +++ b/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs @@ -14,7 +14,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; - +using CommunityToolkit.Mvvm.Input; using Microsoft.Maui.Controls; using Microsoft.Maui; @@ -66,15 +66,15 @@ namespace Bit.App.Pages _clipboardService = ServiceContainer.Resolve("clipboardService"); _watchDeviceService = ServiceContainer.Resolve(); - CopyCommand = new AsyncCommand((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - CopyUriCommand = new AsyncCommand(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - CopyFieldCommand = new AsyncCommand(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + CopyCommand = CreateDefaultAsyncRelayCommand((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + CopyUriCommand = CreateDefaultAsyncRelayCommand(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + CopyFieldCommand = CreateDefaultAsyncRelayCommand(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); LaunchUriCommand = new Command(LaunchUri); - CloneCommand = new AsyncCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); + CloneCommand = CreateDefaultAsyncRelayCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); TogglePasswordCommand = new Command(TogglePassword); ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardCodeCommand = new Command(ToggleCardCode); - DownloadAttachmentCommand = new AsyncCommand(DownloadAttachmentAsync, allowsMultipleExecutions: false); + DownloadAttachmentCommand = CreateDefaultAsyncRelayCommand(DownloadAttachmentAsync, allowsMultipleExecutions: false); PageTitle = AppResources.ViewItem; } @@ -87,7 +87,7 @@ namespace Bit.App.Pages public Command TogglePasswordCommand { get; set; } public Command ToggleCardNumberCommand { get; set; } public Command ToggleCardCodeCommand { get; set; } - public AsyncCommand DownloadAttachmentCommand { get; set; } + public AsyncRelayCommand DownloadAttachmentCommand { get; set; } public string CipherId { get; set; } protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[] { diff --git a/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs b/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs index 7f9e9b79c..c98c467e4 100644 --- a/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs +++ b/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs @@ -45,13 +45,13 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve(); GroupedItems = new ObservableRangeCollection(); - CipherOptionsCommand = new AsyncCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), + CipherOptionsCommand = CreateDefaultAsyncRelayCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - SelectCipherCommand = new AsyncCommand(SelectCipherAsync, + SelectCipherCommand = CreateDefaultAsyncRelayCommand(SelectCipherAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - AddCipherCommand = new AsyncCommand(AddCipherAsync, + AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); diff --git a/src/Core/Pages/Vault/CiphersPageViewModel.cs b/src/Core/Pages/Vault/CiphersPageViewModel.cs index 7eb9e7a05..d4747e2a9 100644 --- a/src/Core/Pages/Vault/CiphersPageViewModel.cs +++ b/src/Core/Pages/Vault/CiphersPageViewModel.cs @@ -52,10 +52,10 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve("logger"); Ciphers = new ExtendedObservableCollection(); - CipherOptionsCommand = new AsyncCommand(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), + CipherOptionsCommand = CreateDefaultAsyncRelayCommand(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - AddCipherCommand = new AsyncCommand(AddCipherAsync, + AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs index 89513cd91..9d39b6fb8 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs @@ -5,7 +5,7 @@ using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; - +using CommunityToolkit.Mvvm.Input; using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Controls; using Microsoft.Maui; @@ -37,13 +37,13 @@ namespace Bit.App.Pages Cipher = cipherView; WebsiteIconsEnabled = websiteIconsEnabled; - CopyCommand = new AsyncCommand(CopyToClipboardAsync, + CopyCommand = CreateDefaultAsyncRelayCommand(CopyToClipboardAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); _totpTickHelper = new TotpHelper(cipherView); } - public AsyncCommand CopyCommand { get; set; } + public AsyncRelayCommand CopyCommand { get; set; } public CipherView Cipher { diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index c33a74a6a..0222973ef 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -71,10 +71,10 @@ namespace Bit.App.Pages Refreshing = true; await LoadAsync(); }); - CipherOptionsCommand = new AsyncCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), + CipherOptionsCommand = CreateDefaultAsyncRelayCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, + VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); diff --git a/src/Core/Pages/Vault/ScanPageViewModel.cs b/src/Core/Pages/Vault/ScanPageViewModel.cs index 04f761866..c9c2495a3 100644 --- a/src/Core/Pages/Vault/ScanPageViewModel.cs +++ b/src/Core/Pages/Vault/ScanPageViewModel.cs @@ -20,7 +20,7 @@ namespace Bit.App.Pages public ScanPageViewModel() { - ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException); + ToggleScanModeCommand = CreateDefaultAsyncRelayCommand(ToggleScanMode, onException: HandleException); StartCameraCommand = new Command(StartCamera); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); diff --git a/src/Core/Pages/Vault/SharePageViewModel.cs b/src/Core/Pages/Vault/SharePageViewModel.cs index d870ad530..e0c73d049 100644 --- a/src/Core/Pages/Vault/SharePageViewModel.cs +++ b/src/Core/Pages/Vault/SharePageViewModel.cs @@ -37,7 +37,7 @@ namespace Bit.App.Pages OrganizationOptions = new List>(); PageTitle = AppResources.MoveToOrganization; - MoveCommand = new AsyncCommand(MoveAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); + MoveCommand = CreateDefaultAsyncRelayCommand(MoveAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } public string CipherId { get; set; } diff --git a/src/Core/Pages/VaultFilterViewModel.cs b/src/Core/Pages/VaultFilterViewModel.cs index 5aa82194b..9791f7f95 100644 --- a/src/Core/Pages/VaultFilterViewModel.cs +++ b/src/Core/Pages/VaultFilterViewModel.cs @@ -25,7 +25,7 @@ namespace Bit.App.Pages public VaultFilterViewModel() { - VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, + VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Resources/Localization/AppResources.Designer.cs b/src/Core/Resources/Localization/AppResources.Designer.cs index a41080f08..8dc58b84f 100644 --- a/src/Core/Resources/Localization/AppResources.Designer.cs +++ b/src/Core/Resources/Localization/AppResources.Designer.cs @@ -6227,6 +6227,15 @@ namespace Bit.Core.Resources.Localization { } } + /// + /// Looks up a localized string similar to Set up an unlock option to change your vault timeout action.. + /// + public static string SetUpAnUnlockOptionToChangeYourVaultTimeoutAction { + get { + return ResourceManager.GetString("SetUpAnUnlockOptionToChangeYourVaultTimeoutAction", resourceCulture); + } + } + /// /// Looks up a localized string similar to Set up TOTP. /// @@ -7766,6 +7775,24 @@ namespace Bit.Core.Resources.Localization { } } + /// + /// Looks up a localized string similar to Your organization permissions were updated, requiring you to set a master password.. + /// + public static string YourOrganizationPermissionsWereUpdatedRequeringYouToSetAMasterPassword { + get { + return ResourceManager.GetString("YourOrganizationPermissionsWereUpdatedRequeringYouToSetAMasterPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your organization requires you to set a master password.. + /// + public static string YourOrganizationRequiresYouToSetAMasterPassword { + get { + return ResourceManager.GetString("YourOrganizationRequiresYouToSetAMasterPassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Your request has been sent to your admin.. /// diff --git a/src/Core/Resources/Localization/AppResources.af.resx b/src/Core/Resources/Localization/AppResources.af.resx index 95b793e58..ea50db841 100644 --- a/src/Core/Resources/Localization/AppResources.af.resx +++ b/src/Core/Resources/Localization/AppResources.af.resx @@ -2861,4 +2861,13 @@ Wil u na die rekening omskakel? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ar.resx b/src/Core/Resources/Localization/AppResources.ar.resx index 3cfc5ce9a..732f17307 100644 --- a/src/Core/Resources/Localization/AppResources.ar.resx +++ b/src/Core/Resources/Localization/AppResources.ar.resx @@ -2751,109 +2751,109 @@ جارٍ تسجيل الدخول - Vault + الخزنة - Appearance + المظهر - Account security + أمان الحساب - Bitwarden Help Center + مركز المساعدة Bitwarden - Contact Bitwarden support + اتصل بالدعم Bitwarden - Copy app information + نسخ معلومات التطبيق - Sync now + المزامنة الآن - Unlock options + خيارات فتح القفل - Session timeout + مهلة الجلسة - Session timeout action + إجراء مهلة الجلسة - Account fingerprint phrase + عبارة بصمة الحساب A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing. - One hour and one minute + ساعة واحدة ودقيقة واحدة - One hour and {0} minutes + ساعة واحدة و {0} دقيقة - {0} hours and one minute + {0} ساعات ودقيقة واحدة - {0} hours and {1} minutes + {0} ساعات و {1} دقيقة - {0} hours + {0} ساعات - The Android Autofill Framework is used to assist in filling login information into other apps on your device. + يتم استخدام إطار التعبئة التلقائية لأندرويد للمساعدة في ملء معلومات تسجيل الدخول في تطبيقات أخرى على جهازك. - Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay. + استخدم التعبئة التلقائية المضمنة إذا كانت لوحة المفاتيح المحددة تدعمها. وإلا استخدم التراكب الافتراضي. - Additional options + خيارات إضافية - Continue to web app? + متابعة إلى تطبيق الويب؟ - Continue to {0}? + الإستمرار إلى {0}؟ The parameter is an URL, like bitwarden.com. - Continue to Help center? + هل تريد المتابعة إلى مركز المساعدة؟ - Continue to contact support? + مواصلة الاتصال بالدعم؟ - Continue to app store? + هل تريد المتابعة إلى متجر التطبيقات؟ - Make your account more secure by setting up two-step login in the Bitwarden web app. + اجعل حسابك أكثر أمنا من خلال إعداد تسجيل الدخول بخطوتين في تطبيق Bitwarden على شبكة الإنترنت. - You can change your master password on the Bitwarden web app. + يمكنك تغيير كلمة المرور الرئيسية الخاصة بك على تطبيق ويب Bitwarden. - You can import data to your vault on {0}. + يمكنك استيراد البيانات إلى خزانتك على {0}. The parameter is an URL, like vault.bitwarden.com. - Learn more about how to use Bitwarden on the Help center. + تعرف على المزيد حول كيفية استخدام Bitwarden في مركز المساعدة. - Can’t find what you are looking for? Reach out to Bitwarden support on bitwarden.com. + لا يمكن العثور على ما تبحث عنه؟ قم بالتواصل مع دعم Bitwarden على bitwarden.com. - Explore more features of your Bitwarden account on the web app. + استكشف المزيد من الميزات لحساب Bitwarden الخاص بك على تطبيق الويب. - Bitwarden allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website. + يتيح لك Bitwarden مشاركة عناصر خزنتك مع الآخرين باستخدام حساب المؤسسة. تعرف على المزيد على موقع bitwarden.com على شبكة الإنترنت. - Help others find out if Bitwarden is right for them. Visit the app store and leave a rating now. + ساعد الآخرين في معرفة ما إذا كان Bitwarden مناسبا لهم. قم بزيارة متجر التطبيقات وترك التقييم الآن. - Choose the dark theme to use when your device’s dark mode is in use + اختر السمة المظلمة لاستخدامها عند استخدام الوضع المظلم لجهازك - Created {0}, {1} + أنشئ {0}، {1} To state the date/time in which the cipher was created: Created 03/21/2023, 09:25 AM. First parameter is the date and the second parameter is the time. @@ -2862,4 +2862,13 @@ تم تسجيل الخروج من الحساب. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.az.resx b/src/Core/Resources/Localization/AppResources.az.resx index f84632239..ac48e364c 100644 --- a/src/Core/Resources/Localization/AppResources.az.resx +++ b/src/Core/Resources/Localization/AppResources.az.resx @@ -2860,4 +2860,13 @@ Bu hesaba keçmək istəyirsiniz? Hesabdan çıxış edildi. + + Təşkilatınızın icazələri güncəlləndi və bir ana parol ayarlamağınızı tələb edir. + + + Təşkilatınız bir ana parol ayarlamağı tələb edir. + + + Anbar vaxt bitməsi əməliyyatınızı dəyişdirmək üçün bir kilid açma seçimi qurun. + diff --git a/src/Core/Resources/Localization/AppResources.be.resx b/src/Core/Resources/Localization/AppResources.be.resx index 7d7b8f48c..4101d64f2 100644 --- a/src/Core/Resources/Localization/AppResources.be.resx +++ b/src/Core/Resources/Localization/AppResources.be.resx @@ -2861,4 +2861,13 @@ Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.bg.resx b/src/Core/Resources/Localization/AppResources.bg.resx index d49909532..e75576e34 100644 --- a/src/Core/Resources/Localization/AppResources.bg.resx +++ b/src/Core/Resources/Localization/AppResources.bg.resx @@ -2861,4 +2861,13 @@ select Add TOTP to store the key safely Акаунтът е отписан. + + Правата Ви в организацията бяха променени, необходимо е да зададете главна парола. + + + Организацията Ви изисква да зададете главна парола. + + + Задайте начин за отключване, за да може да промените действието при изтичане на времето за достъп до трезора. + diff --git a/src/Core/Resources/Localization/AppResources.bn.resx b/src/Core/Resources/Localization/AppResources.bn.resx index 20a646a97..e7aeda589 100644 --- a/src/Core/Resources/Localization/AppResources.bn.resx +++ b/src/Core/Resources/Localization/AppResources.bn.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.bs.resx b/src/Core/Resources/Localization/AppResources.bs.resx index 91c492d93..01905c2a2 100644 --- a/src/Core/Resources/Localization/AppResources.bs.resx +++ b/src/Core/Resources/Localization/AppResources.bs.resx @@ -2860,4 +2860,13 @@ Skeniranje će biti izvršeno automatski. Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ca.resx b/src/Core/Resources/Localization/AppResources.ca.resx index 4c83ca308..9b6799c3c 100644 --- a/src/Core/Resources/Localization/AppResources.ca.resx +++ b/src/Core/Resources/Localization/AppResources.ca.resx @@ -2861,4 +2861,13 @@ Voleu canviar a aquest compte? S'ha tancat la sessió del compte. + + Els permisos de la vostra organització s'han actualitzat, cal que establiu una contrasenya mestra. + + + La vostra organització requereix que establiu una contrasenya mestra. + + + Configura una opció de desbloqueig per canviar l'acció de temps d'espera de la caixa forta. + diff --git a/src/Core/Resources/Localization/AppResources.cs.resx b/src/Core/Resources/Localization/AppResources.cs.resx index 4c31da697..9f328ae27 100644 --- a/src/Core/Resources/Localization/AppResources.cs.resx +++ b/src/Core/Resources/Localization/AppResources.cs.resx @@ -375,7 +375,7 @@ Validation message for when a form field is left blank and is required to be entered. - {0} bylo zkopírováno. + {0}: zkopírováno Confirmation message after successfully copying a value to the clipboard. @@ -499,7 +499,7 @@ Pro spuštění rozšíření klepněte na ikonu Bitwardenu v menu. - Pro zapnutí Bitwardenu v prohlížeči Safari a dalších aplikacích klepněte na ikonu „Další“ v dolní části menu. + Pro zapnutí Bitwardenu v prohlížeči Safari a dalších aplikacích klepněte na ikonu "Další“ v dolní části menu. Oblíbené @@ -958,10 +958,10 @@ Načtení proběhne automaticky. Vlastní prostředí - Pro pokročilé uživatele. Můžete zadat základní URL adresu každé služby zvlášť. + Pro pokročilé uživatele. Můžete zadat základní URL každé služby zvlášť. - URL adresy vlastního prostředí byly uloženy. + URL vlastního prostředí byly uloženy. {0} nemá správný formát. @@ -975,7 +975,7 @@ Načtení proběhne automaticky. Vlastní hostované prostředí - Zadejte základní URL adresu vlastní hostované aplikace Bitwarden. + Zadejte základní URL vlastní hostované aplikace Bitwarden. URL serveru @@ -1023,13 +1023,13 @@ Načtení proběhne automaticky. Adresa 3 - Duben + duben - Srpen + srpen - Značka + Vydavatel Jméno držitele karty @@ -1044,10 +1044,10 @@ Načtení proběhne automaticky. Stát - Prosinec + prosinec - MUDr. + Dr. Měsíc expirace @@ -1056,19 +1056,19 @@ Načtení proběhne automaticky. Rok expirace - Únor + únor Křestní jméno - Leden + leden - Červenec + červenec - Červen + červen Příjmení @@ -1080,10 +1080,10 @@ Načtení proběhne automaticky. Číslo dokladu totožnosti - Březen + březen - Květen + květen Prostřední jméno @@ -1101,10 +1101,10 @@ Načtení proběhne automaticky. Neutrální - Listopad + listopad - Říjen + říjen Číslo cestovního pasu @@ -1113,10 +1113,10 @@ Načtení proběhne automaticky. Telefon - Září + září - Číslo sociálního pojištění + Rodné číslo Kraj / Provincie @@ -1298,22 +1298,22 @@ Načtení proběhne automaticky. Přistupujte k Vašemu trezoru přímo z Vaší klávesnice pro rychlejší automatické vyplnění hesel. - Pokyny pro zapnutí automatického vyplňování hesel na vašem zařízení: + Pokyny pro zapnutí automatického vyplňování hesel na Vašem zařízení: 1. Přejděte do aplikace "Nastavení" v iOS - 2. Klepněte na „Hesla“ > „Volby hesla“ + 2. Klepněte na "Hesla" > "Volby hesla" - 3. Povolte „Automatické vyplnění hesel“ + 3. Zapněte přepínač u položky "Automatické vyplnění hesel" - 4. Najděte sekci „Povolit vyplňování z“ + 4. Najděte sekci "Povolit vyplňování z:" - 5. Zvolte „Bitwarden“ + 5. Zvolte "Bitwarden" Automatické vyplňování hesel @@ -1562,7 +1562,7 @@ Načtení proběhne automaticky. Chcete po restartování aplikace vyžadovat hlavní heslo pro odemknutí trezoru? - Černá + Černý The color black @@ -1570,7 +1570,7 @@ Načtení proběhne automaticky. 'Nord' is the name of a specific color scheme. It should not be translated. - Tmavý –⁠ Solarized + Tmavý (solární) 'Solarized Dark' is the name of a specific color scheme. It should not be translated. @@ -1947,7 +1947,7 @@ Načtení proběhne automaticky. Nové heslo - Volitelně vyžadovat heslo pro přístup k tomuto Send. + Volitelně bude vyžadovat heslo pro přístup k tomuto Send. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -1967,7 +1967,7 @@ Načtení proběhne automaticky. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Deaktivuje tento Send, díky čemuž k němu nebude moci nikdo přistoupit + Deaktivovat tento Send (nikdo k němu nebude mít přístup) 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2299,13 +2299,13 @@ Načtení proběhne automaticky. Nastavení zámku na "Nikdy" ponechá Váš trezor k dispozici komukoli s přístupem k Vašemu zařízení. Používáte-li tuto možnost, měli byste zajistit, aby Vaše zařízení bylo náležitě chráněno. - Jedna nebo více zadaných adres URL jsou neplatné. Zkontrolujte je a opakujte akci znovu. + Jedna nebo více zadaných URL jsou neplatné. Zkontrolujte je a opakujte akci znovu. Nepodařilo se nám zpracovat Váš požadavek. Zkuste to znovu nebo nás kontaktujte. - Povolit záznam obrazovky + Zapnout záznam obrazovky Opravdu chcete zapnout záznam obrazovky? @@ -2544,7 +2544,7 @@ Chcete se přepnout na tento účet? Jazyk byl změněn na {0}. Pro zobrazení změn restartujte aplikaci. - Změna jazyku vyžaduje restart aplikace + Změna jazyka vyžaduje restart aplikace. Výchozí (Systémový) @@ -2860,4 +2860,13 @@ Chcete se přepnout na tento účet? Účet byl odhlášen. + + Oprávnění Vaší organizace byla aktualizována. To vyžaduje nastavení hlavního hesla. + + + Vaše organizace vyžaduje nastavení hlavního hesla. + + + Nastavte volbu odemknutí, abyste změnili časový limit Vašeho trezoru. + diff --git a/src/Core/Resources/Localization/AppResources.cy.resx b/src/Core/Resources/Localization/AppResources.cy.resx index 8397e7476..a7a01eb33 100644 --- a/src/Core/Resources/Localization/AppResources.cy.resx +++ b/src/Core/Resources/Localization/AppResources.cy.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.da.resx b/src/Core/Resources/Localization/AppResources.da.resx index 431c53d91..2d0cdb3dc 100644 --- a/src/Core/Resources/Localization/AppResources.da.resx +++ b/src/Core/Resources/Localization/AppResources.da.resx @@ -2861,4 +2861,13 @@ Vil du skifte til denne konto? Konto logget ud. + + Organisationstilladelserne er blevet opdateret, og der kræves nu oprettelse af en hovedadgangskode. + + + Organisationen kræver, at der oprettes en hovedadgangskode. + + + Opsæt en oplåsningsmetode for at ændre Bokstimeouthandlingen. + diff --git a/src/Core/Resources/Localization/AppResources.de.resx b/src/Core/Resources/Localization/AppResources.de.resx index f524463f6..7ef68b1e5 100644 --- a/src/Core/Resources/Localization/AppResources.de.resx +++ b/src/Core/Resources/Localization/AppResources.de.resx @@ -946,7 +946,7 @@ Das Scannen erfolgt automatisch. Du kannst diese Funktion nicht nutzen, solange du deinen Verschlüsselungsschlüssel nicht aktualisiert hast. - Encryption key migration required. Please login through the web vault to update your encryption key. + Verschlüsselungscode-Migration erforderlich. Bitte melde dich über den Web-Tresor an, um deinen Verschlüsselungscode zu aktualisieren. Mehr erfahren @@ -2860,4 +2860,13 @@ Möchtest du zu diesem Konto wechseln? Konto abgemeldet. + + Deine Organisationsberechtigungen wurden aktualisiert und verlangen, dass du ein Master-Passwort festlegen musst. + + + Deine Organisation verlangt, dass du ein Master-Passwort festlegen musst. + + + Richte eine Entsperroption ein, um deine Aktion bei Tresor-Timeout zu ändern. + diff --git a/src/Core/Resources/Localization/AppResources.el.resx b/src/Core/Resources/Localization/AppResources.el.resx index e47fec6f0..b2a28cdd5 100644 --- a/src/Core/Resources/Localization/AppResources.el.resx +++ b/src/Core/Resources/Localization/AppResources.el.resx @@ -247,8 +247,7 @@ Description message for the alert when internet connection is required to continue. - 28/5000 -Απαιτείται σύνδεση στο διαδίκτυο + Απαιτείται σύνδεση στο διαδίκτυο Title for the alert when internet connection is required to continue. @@ -946,7 +945,7 @@ Δεν μπορείτε να χρησιμοποιήσετε αυτήν τη δυνατότητα μέχρι να ενημερώσετε το κλειδί κρυπτογράφησης. - Encryption key migration required. Please login through the web vault to update your encryption key. + Απαιτείται μεταφορά του κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του web vault για να ενημερώσετε το κλειδί κρυπτογράφησης. Μάθετε Περισσότερα @@ -2623,28 +2622,28 @@ Τρέχων κύριος κωδικός - Logged in! + Έχετε συνδεθεί! - Approve with my other device + Έγκριση μέσω άλλης συσκευής μου - Request admin approval + Αίτηση έγκρισης από διαχειριστή - Approve with master password + Έγκριση με τον κύριο κωδικό πρόσβασης - Turn off using a public device + Απενεργοποίηση με χρήση δημόσιας συσκευής - Remember this device + Απομνημόνευση αυτής της συσκευής - Συνθηματικό + Κλειδί πρόσβασης - Συνθηματικά + Κλειδιά πρόσβασης Εφαρμογή @@ -2659,13 +2658,13 @@ Το κλειδί πρόσβασης δεν θα αντιγραφεί στο κλωνοποιημένο στοιχείο. Θέλετε να συνεχίσετε την κλωνοποίηση αυτού του στοιχείου; - Copy application + Αντιγραφή εφαρμογής Διαθέσιμο για σύνδεση με δύο βήματα - Master password re-prompt help + Βοήθεια προτροπής κύριου κωδικού πρόσβασης Το ξεκλείδωμα μπορεί να αποτύχει λόγω ανεπαρκούς μνήμης. Μειώστε τις ρυθμίσεις μνήμης KDF ή ρυθμίστε το βιομετρικό ξεκλείδωμα για επίλυση. @@ -2677,188 +2676,197 @@ Μη έγκυρο API token - Admin approval requested + Ζητήθηκε έγκριση διαχειριστή - Your request has been sent to your admin. + Το αίτημά σας έχει σταλεί στον διαχειριστή σας. - You will be notified once approved. + Θα ειδοποιηθείτε μόλις εγκριθεί. - Trouble logging in? + Δεν μπορείτε να συνδεθείτε; - Logging in as {0} + Σύνδεση ως {0} - Vault timeout action changed to log out + Η ενέργεια στη λήξη χρόνου του vault άλλαξε σε αποσύνδεση - Block auto-fill + Αποκλείστε την αυτόματη συμπλήρωση - Auto-fill will not be offered for these URIs. + Η αυτόματη συμπλήρωση δε θα προσφέρεται για αυτά τα URI. - New blocked URI + Νέο αποκλεισμένο URI - URI saved + URI αποθηκεύτηκε - Invalid format. Use https://, http://, or androidapp:// + Μη έγκυρη μορφή. Χρησιμοποιήστε https://, http://, ή androidapp:// https://, http://, androidapp:// should not be translated - Edit URI + Επεξεργασία του URI - Enter URI + Καταχωρήστε URI - Format: {0}. Separate multiple URIs with a comma. + Μορφή: {0}. Διαχωρίστε τα πολλαπλά URI με κόμμα. - Format: {0} + Μορφή: {0} - Invalid URI + Μη έγκυρο URI - URI removed + Το URI αφαιρέθηκε - There are no blocked URIs + Δεν υπάρχουν αποκλεισμένα URI - The URI {0} is already blocked + Το URI {0} είναι ήδη αποκλεισμένο - Cannot edit multiple URIs at once + Αδυναμία επεξεργασίας πολλαπλών URI ταυτόχρονα - Login approved + Η σύνδεση εγκρίθηκε - Log in with device must be set up in the settings of the Bitwarden app. Need another option? + Η σύνδεση με χρήση συσκευής πρέπει να ρυθμιστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε άλλη επιλογή; - Log in with device + Σύνδεση με χρήση συσκευής - Logging in on + Σύνδεση στο Vault - Appearance + Εμφάνιση - Account security + Ασφάλεια λογαριασμού - Bitwarden Help Center + Κέντρο Βοήθειας Bitwarden - Contact Bitwarden support + Επικοινωνία με την υποστήριξη του Bitwarden - Copy app information + Αντιγραφή πληροφοριών εφαρμογής - Sync now + Συγχρονισμός τώρα - Unlock options + Επιλογές Κλειδώματος - Session timeout + Χρονικό όριο συνεδρίας - Session timeout action + Ενέργεια στη λήξη χρόνου συνεδρίας - Account fingerprint phrase + "Φράση αποτυπώματος" λογαριασμού A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing. - One hour and one minute + Μία ώρα και ένα λεπτό - One hour and {0} minutes + Μία ώρα και {0} λεπτά - {0} hours and one minute + {0} ώρες και ένα λεπτό - {0} hours and {1} minutes + {0} ώρες και {1} λεπτά - {0} hours + {0} ώρες - The Android Autofill Framework is used to assist in filling login information into other apps on your device. + Το Android Autofill Framework χρησιμοποιείται για να προσφέρει αυτόματη συμπλήρωση στοιχείων σύνδεσης σε άλλες εφαρμογές στη συσκευή σας. - Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay. + Χρησιμοποιήστε την ενσωματωμένη αυτόματη συμπλήρωση αν το πληκτρολόγιο σας την υποστηρίζει. Διαφορετικά, χρησιμοποιήστε την προεπιλεγμένη επικάλυψη. - Additional options + Πρόσθετες επιλογές - Continue to web app? + Συνέχεια στην εφαρμογή διαδικτύου; - Continue to {0}? + Συνέχεια στο {0}; The parameter is an URL, like bitwarden.com. - Continue to Help center? + Συνέχεια στο κέντρο βοήθειας; - Continue to contact support? + Συνέχεια στην επικοινωνία με την υποστήριξη; - Continue to app store? + Συνέχεια στο κατάστημα εφαρμογών; - Make your account more secure by setting up two-step login in the Bitwarden web app. + Κάντε τον λογαριασμό σας πιο ασφαλή με τη ρύθμιση δύο βημάτων σύνδεσης στην εφαρμογή διαδικτύου Bitwarden. - You can change your master password on the Bitwarden web app. + Μπορείτε να αλλάξετε τον κύριο κωδικό πρόσβασης στην εφαρμογή διαδικτύου Bitwarden. - You can import data to your vault on {0}. + Μπορείτε να εισαγάγετε δεδομένα στο vault σας στο {0}. The parameter is an URL, like vault.bitwarden.com. - Learn more about how to use Bitwarden on the Help center. + Μάθετε περισσότερα για το πώς να χρησιμοποιήσετε το Bitwarden στο κέντρο βοήθειας. - Can’t find what you are looking for? Reach out to Bitwarden support on bitwarden.com. + Δεν μπορείτε να βρείτε αυτό που ψάχνετε; Επικοινωνήστε με την υποστήριξη Bitwarden στο bitwarden.com. - Explore more features of your Bitwarden account on the web app. + Εξερευνήστε περισσότερες δυνατότητες του Bitwarden λογαριασμού σας, στην εφαρμογή διαδικτύου. - Bitwarden allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website. + Το Bitwarden σας επιτρέπει να μοιράζεστε τα στοιχεία του vault σας με άλλους, χρησιμοποιώντας έναν λογαριασμό οργανισμού. Μάθετε περισσότερα στην ιστοσελίδα bitwarden.com. - Help others find out if Bitwarden is right for them. Visit the app store and leave a rating now. + Βοηθήστε άλλους να μάθουν αν το Bitwarden είναι κατάλληλο για αυτούς. Επισκεφθείτε το κατάστημα εφαρμογών και αφήστε τώρα μια βαθμολογία. - Choose the dark theme to use when your device’s dark mode is in use + Επιλέξτε να χρησιμοποιείται το σκούρο θέμα όταν η συσκευή σας βρίσκεται σε σκοτεινή λειτουργία - Created {0}, {1} + Δημιουργήθηκε {0}, {1} To state the date/time in which the cipher was created: Created 03/21/2023, 09:25 AM. First parameter is the date and the second parameter is the time. - Too many attempts + Πάρα πολλές προσπάθειες - Account logged out. + Ο λογαριασμός αποσυνδέθηκε. + + + Τα δικαιώματα του οργανισμού σας ενημερώθηκαν, απαιτώντας από εσάς να ορίσετε έναν κύριο κωδικό πρόσβασης. + + + Ο οργανισμός σας απαιτεί να ορίσετε έναν κύριο κωδικό πρόσβασης. + + + Ρυθμίστε μια επιλογή κλειδώματος για να αλλάξετε την ενέργεια στη λήξη χρόνου του vault σας. diff --git a/src/Core/Resources/Localization/AppResources.en-GB.resx b/src/Core/Resources/Localization/AppResources.en-GB.resx index 49ffeba91..53ebf0336 100644 --- a/src/Core/Resources/Localization/AppResources.en-GB.resx +++ b/src/Core/Resources/Localization/AppResources.en-GB.resx @@ -2861,4 +2861,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.en-IN.resx b/src/Core/Resources/Localization/AppResources.en-IN.resx index a978e32ce..c56f1a6fd 100644 --- a/src/Core/Resources/Localization/AppResources.en-IN.resx +++ b/src/Core/Resources/Localization/AppResources.en-IN.resx @@ -2875,4 +2875,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.es.resx b/src/Core/Resources/Localization/AppResources.es.resx index 184c83cf2..72f9a2af1 100644 --- a/src/Core/Resources/Localization/AppResources.es.resx +++ b/src/Core/Resources/Localization/AppResources.es.resx @@ -2862,4 +2862,13 @@ seleccione Agregar TOTP para almacenar la clave de forma segura Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.et.resx b/src/Core/Resources/Localization/AppResources.et.resx index df4574642..bbc702cee 100644 --- a/src/Core/Resources/Localization/AppResources.et.resx +++ b/src/Core/Resources/Localization/AppResources.et.resx @@ -2861,4 +2861,13 @@ Soovid selle konto peale lülituda? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.eu.resx b/src/Core/Resources/Localization/AppResources.eu.resx index fd897dafe..dbb0407dc 100644 --- a/src/Core/Resources/Localization/AppResources.eu.resx +++ b/src/Core/Resources/Localization/AppResources.eu.resx @@ -2860,4 +2860,13 @@ Kontu honetara aldatu nahi duzu? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.fa.resx b/src/Core/Resources/Localization/AppResources.fa.resx index f3cb074fe..58931a9d6 100644 --- a/src/Core/Resources/Localization/AppResources.fa.resx +++ b/src/Core/Resources/Localization/AppResources.fa.resx @@ -2862,4 +2862,13 @@ حساب از سیستم خارج شد. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.fi.resx b/src/Core/Resources/Localization/AppResources.fi.resx index 31ebaaca6..66692ea4f 100644 --- a/src/Core/Resources/Localization/AppResources.fi.resx +++ b/src/Core/Resources/Localization/AppResources.fi.resx @@ -2862,4 +2862,13 @@ Haluatko vaihtaa tähän tiliin? Tili kirjattiin ulos. + + Organisaatiosi käyttöoikeuksia muutettiin ja tämän seurauksena sinun on asetettava pääsalasana. + + + Organisaatiosi edellyttää, että asetat pääsalasanan. + + + Muuta holvisi aikakatkaisutoimintoa määrittämällä lukituksen avaustapa. + diff --git a/src/Core/Resources/Localization/AppResources.fil.resx b/src/Core/Resources/Localization/AppResources.fil.resx index 57ea2bfb9..61f125f13 100644 --- a/src/Core/Resources/Localization/AppResources.fil.resx +++ b/src/Core/Resources/Localization/AppResources.fil.resx @@ -2862,4 +2862,13 @@ Gusto mo bang pumunta sa account na ito? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.fr.resx b/src/Core/Resources/Localization/AppResources.fr.resx index 22b52c9dd..c804fce4a 100644 --- a/src/Core/Resources/Localization/AppResources.fr.resx +++ b/src/Core/Resources/Localization/AppResources.fr.resx @@ -455,7 +455,7 @@ Continuer - Créer un compte + Créez un compte Création du compte... @@ -686,10 +686,10 @@ Message shown when interacting with the server - Synchronisation terminée + Synchronisation achevée - Échec de la synchronisation + Synchronisation échouée Synchroniser le coffre maintenant @@ -778,7 +778,7 @@ Le moyen le plus simple d'ajouter de nouveaux identifiants à votre coffre est d'utiliser le service de saisie automatique Bitwarden. Pour en savoir davantage sur l'utilisation du service de saisie automatique Bitwarden, naviguez jusqu'à l'écran "Paramètres". - Saisir automatiquement + Saisie automatique Voulez-vous saisir automatiquement ou afficher cet élément ? @@ -907,7 +907,7 @@ La numérisation se fera automatiquement. Copier le TOTP - Si un identifiant a une clé d'authentification, copier le code de vérification TOTP dans votre presse-papier quand vous remplissez automatiquement l'identifiant. + Si un identifiant possède une clé d'authentification, copiez le code de vérification TOTP dans votre presse-papiers lorsque vous saisissez automatiquement l'identifiant. Copier TOTP automatiquement @@ -1134,7 +1134,7 @@ La numérisation se fera automatiquement. Expiration - Afficher les icônes du site web + Afficher les icônes des sites web Affichez une image reconnaissable à côté de chaque identifiant. @@ -1304,13 +1304,13 @@ La numérisation se fera automatiquement. 1. Allez dans l'application "Réglages" d'iOS - 2. Appuyez sur "Mots de passe et comptes" + 2. Appuyez sur "Mots de passe" - 3. Appuyez sur "Préremplir mots de passe" + 3. Appuyez sur "Options des mots de passe" - 4. Activez "Préremplir mots de passe" + 4. Activez "Remplir automatiquement les mots de passe et les clés d'identification" 5. Sélectionnez "Bitwarden" @@ -1378,10 +1378,10 @@ La numérisation se fera automatiquement. Rechercher dans la collection - Rechercher des fichiers Sends + Rechercher des Send fichier - Rechercher des textes Sends + Rechercher des Sends texte Rechercher {0} : @@ -1400,7 +1400,7 @@ La numérisation se fera automatiquement. Divers - Propriété + Propriétaire À qui appartient cet élément ? @@ -1431,7 +1431,7 @@ La numérisation se fera automatiquement. Aucune organisation à lister. - Choisissez une organisation vers laquelle vous souhaitez déplacer cet élément. Déplacer un élément vers une organisation transfère la propriété de l'élément à cette organisation. Vous ne serez plus le propriétaire direct de cet élément une fois qu'il aura été déplacé. + Choisissez une organisation vers laquelle vous souhaitez déplacer cet élément. Le déplacement vers une organisation transfère le Propriétaire de l'élément à cette organisation. Vous ne serez plus le propriétaire direct de cet élément une fois qu'il aura été déplacé. Nombre de mots @@ -1526,7 +1526,7 @@ La numérisation se fera automatiquement. Clipboard is the operating system thing where you copy/paste data to on your device. - Effacer automatiquement de votre presse-papiers les valeurs copiées. + Effacez automatiquement les valeurs copiées de votre presse-papiers. Clipboard is the operating system thing where you copy/paste data to on your device. @@ -1750,7 +1750,7 @@ La numérisation se fera automatiquement. Autoriser la synchronisation au rafraîchissement - Synchronisation du coffre avec un geste vers le bas + Synchronisez le coffre avec un geste vers le bas. Portail de connexion unique d'entreprise @@ -1853,10 +1853,10 @@ La numérisation se fera automatiquement. Si cette option est activée, l'accessibilité affichera une popup pour améliorer le service de remplissage automatique pour les anciennes applications qui ne prennent pas en charge les outils de remplissage automatique d'Android. - En raison d'une Politique d'Entreprise, il vous est interdit d'enregistrer des objets dans votre coffre personnel. Changez l'option Propriété pour une organisation et choisissez parmi les Collections disponibles. + En raison d'une politique d'entreprise, il vous est interdit d'enregistrer des éléments dans votre coffre personnel. Changez l'option Propriétaire au profit d'une organisation et choisissez parmi les collections disponibles. - Une politique d'organisation affecte vos options de propriété. + Une politique d'organisation affecte vos options de Propriétaire. Send @@ -1968,7 +1968,7 @@ La numérisation se fera automatiquement. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Désactiver cet envoi pour que personne ne puisse y accéder. + Désactiver ce Send 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. @@ -1998,7 +1998,7 @@ La numérisation se fera automatiquement. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Ajouter un Send + Nouveau Send 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2006,7 +2006,7 @@ La numérisation se fera automatiquement. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Le Send a été supprimé. + Send supprimé 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2014,7 +2014,7 @@ La numérisation se fera automatiquement. 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. - Nouveau Send créé. + Send créé 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2036,7 +2036,7 @@ La numérisation se fera automatiquement. Personnalisé - Partager ce Send lors de l'enregistrement. + Partagez ce Send au moment de l'enregistrement 'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated. @@ -2355,7 +2355,7 @@ sélectionnez Ajouter TOTP pour stocker la clé en toute sécurité Approuver les demandes de connexion - Utiliser cet appareil pour approuver les demandes de connexion faites à partir d'autres appareils. + Utiliser cet appareil pour approuver les demandes de connexion faites à partir d'autres appareils Autoriser les notifications @@ -2546,7 +2546,7 @@ Voulez-vous basculer vers ce compte ? La langue a été changée en {0}. Veuillez redémarrer l'application pour voir le changement - Le changement de langue nécessite le redémarrage de l'application + Le changement de la langue nécessite le redémarrage de l'application. Par défaut (système) @@ -2781,7 +2781,7 @@ Voulez-vous basculer vers ce compte ? Action après délai d'expiration de la session - La phrase d'empreinte du compte + Phrase d'empreinte du compte A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing. @@ -2862,4 +2862,13 @@ Voulez-vous basculer vers ce compte ? Compte déconnecté. + + Les autorisations de votre organisation ont été mises à jour, vous obligeant à définir un mot de passe principal. + + + Votre organisation vous demande de définir un mot de passe principal. + + + Configurez une méthode de déverrouillage pour modifier l'action après délai d'expiration de votre coffre. + diff --git a/src/Core/Resources/Localization/AppResources.gl.resx b/src/Core/Resources/Localization/AppResources.gl.resx index 7d8f4f604..47405c13b 100644 --- a/src/Core/Resources/Localization/AppResources.gl.resx +++ b/src/Core/Resources/Localization/AppResources.gl.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.he.resx b/src/Core/Resources/Localization/AppResources.he.resx index 04425eda2..1a4ed925a 100644 --- a/src/Core/Resources/Localization/AppResources.he.resx +++ b/src/Core/Resources/Localization/AppResources.he.resx @@ -2864,4 +2864,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.hi.resx b/src/Core/Resources/Localization/AppResources.hi.resx index 318524e40..596cfd7f7 100644 --- a/src/Core/Resources/Localization/AppResources.hi.resx +++ b/src/Core/Resources/Localization/AppResources.hi.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.hr.resx b/src/Core/Resources/Localization/AppResources.hr.resx index 81d4e1e0e..21ee81df9 100644 --- a/src/Core/Resources/Localization/AppResources.hr.resx +++ b/src/Core/Resources/Localization/AppResources.hr.resx @@ -2423,7 +2423,7 @@ "Fastmail" is the product name and should not be translated. - ForwardEmail + FowardEmail "ForwardEmail" is the product name and should not be translated. @@ -2748,106 +2748,106 @@ Prijava na - Vault + Trezor - Appearance + Izgled - Account security + Sigurnost računa - Bitwarden Help Center + Bitwarden centar za pomoć - Contact Bitwarden support + Kontaktiraj Bitwarden pomoć - Copy app information + Kopiraj podake o aplikaciji - Sync now + Sinkroniziraj - Unlock options + Otključaj mogućnosti - Session timeout + Istek sesije - Session timeout action + Radnja kod isteka sesije - Account fingerprint phrase + Jedinstvena fraza računa A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing. - One hour and one minute + Jedan sat i jedna minuta - One hour and {0} minutes + Jedan sat i {0} minuta - {0} hours and one minute + {0} sat/i i jedna minuta - {0} hours and {1} minutes + {0} sat/i {1} minuta - {0} hours + {0} sat/i - The Android Autofill Framework is used to assist in filling login information into other apps on your device. + Android Autofill Framework se koristi za pomoć pri ispunjavanju prijava, platnih kartica i identifikacijskih podataka u drugim aplikacijama na tvojem uređaju. - Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay. + Koristi izravnu auto-ispunu ako ga tvoja odabrana tipkovnica podržava. U suprotnom, koristit će se zadana usluga auto-ispune. - Additional options + Dodatne mogućnosti - Continue to web app? + Nastavi na web aplikaciju? - Continue to {0}? + Nastavi na {0}? The parameter is an URL, like bitwarden.com. - Continue to Help center? + Nastavi u centar za pomoć? - Continue to contact support? + Kontaktiraj podršku? - Continue to app store? + Nastavi u trgovinu aplikacijama? - Make your account more secure by setting up two-step login in the Bitwarden web app. + Učini svoj račun sigurnijim uključivanjem prijave dvofaktorskom autentifikacijom u Bitwarden web aplikaciji. - You can change your master password on the Bitwarden web app. + Svoju lozinku možeš promijeniti u Bitwarden web aplikaciji. - You can import data to your vault on {0}. + Svoje podatke možeš uvesti u trezor na {0}. The parameter is an URL, like vault.bitwarden.com. - Learn more about how to use Bitwarden on the Help center. + Za pomoć oko korištenja Bitwardena posjeti centar za pomoć. - Can’t find what you are looking for? Reach out to Bitwarden support on bitwarden.com. + Ne možeš naći što te zanima? Kontaktiraj Bitwarden podršku na bitwarden.com. - Explore more features of your Bitwarden account on the web app. + Pronađi viđe značajki svojeg Bitwarden računa u web aplikaciji. - Bitwarden allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website. + Bitwarden omogućuje dijeljenje trezora s drugima pomoću organizacijskog računa. Za više informacija posjeti bitwarden.com. - Help others find out if Bitwarden is right for them. Visit the app store and leave a rating now. + Želiš preporučiti Bitwarden drugima? Posjeti app store i ostavi recenziju. - Choose the dark theme to use when your device’s dark mode is in use + Odaberi tamnu temu kada se tvoj uređaj nalazi u tamnom načinu rada Stvoreno {0}, {1} @@ -2859,4 +2859,13 @@ Račun odjavljen. + + Moraš postaviti glavnu lozinku jer su dopuštenja tvoje organizacije ažurirana. + + + Tvoja organizacija zahtijeva da postaviš glavnu lozinku. + + + Za promjenu vremena isteka trezora, odredi način otključavanja. + diff --git a/src/Core/Resources/Localization/AppResources.hu.resx b/src/Core/Resources/Localization/AppResources.hu.resx index ad4953e3a..ee800218f 100644 --- a/src/Core/Resources/Localization/AppResources.hu.resx +++ b/src/Core/Resources/Localization/AppResources.hu.resx @@ -2860,4 +2860,13 @@ Szeretnénk átváltani erre a fiókra? A fiók kijelentkezett. + + A szervezeti jogosultságok frissítésre kerültek, ezért be kell állítani egy mesterjelszót. + + + A szervezet megköveteli egy mesterjelszó beállítását. + + + Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához. + diff --git a/src/Core/Resources/Localization/AppResources.id.resx b/src/Core/Resources/Localization/AppResources.id.resx index 86aa13b48..00b069f4a 100644 --- a/src/Core/Resources/Localization/AppResources.id.resx +++ b/src/Core/Resources/Localization/AppResources.id.resx @@ -2861,4 +2861,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.it.resx b/src/Core/Resources/Localization/AppResources.it.resx index e567f1240..156467c8c 100644 --- a/src/Core/Resources/Localization/AppResources.it.resx +++ b/src/Core/Resources/Localization/AppResources.it.resx @@ -2695,7 +2695,7 @@ Vuoi passare a questo account? Azione timeout cassaforte impostata su uscire - Bocca riempimento automatico + Blocca riempimento automatico Il riempimento automatico non sarà offerto per questi URI. @@ -2861,4 +2861,13 @@ Vuoi passare a questo account? Account uscito. + + Le autorizzazioni della tua organizzazione sono state aggiornate, obbligandoti a impostare una password principale. + + + La tua organizzazione ti obbliga di impostare di una password principale. + + + Imposta un metodo di sblocco per modificare l'azione timeout cassaforte. + diff --git a/src/Core/Resources/Localization/AppResources.ja.resx b/src/Core/Resources/Localization/AppResources.ja.resx index d681f83c4..92ac34eae 100644 --- a/src/Core/Resources/Localization/AppResources.ja.resx +++ b/src/Core/Resources/Localization/AppResources.ja.resx @@ -2861,4 +2861,13 @@ アカウントからログアウトしました。 + + 組織の権限が更新され、マスターパスワードの設定が必要になりました。 + + + あなたの組織では、マスターパスワードの設定が義務付けられています。 + + + 保管庫のタイムアウト動作を変更するには、ロック解除方法を設定してください。 + diff --git a/src/Core/Resources/Localization/AppResources.ka.resx b/src/Core/Resources/Localization/AppResources.ka.resx index 7d8f4f604..47405c13b 100644 --- a/src/Core/Resources/Localization/AppResources.ka.resx +++ b/src/Core/Resources/Localization/AppResources.ka.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.kn.resx b/src/Core/Resources/Localization/AppResources.kn.resx index 75d4ba7f2..07dcdc74f 100644 --- a/src/Core/Resources/Localization/AppResources.kn.resx +++ b/src/Core/Resources/Localization/AppResources.kn.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ko.resx b/src/Core/Resources/Localization/AppResources.ko.resx index 244a14aa6..0c739e5e3 100644 --- a/src/Core/Resources/Localization/AppResources.ko.resx +++ b/src/Core/Resources/Localization/AppResources.ko.resx @@ -2861,4 +2861,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.lt.resx b/src/Core/Resources/Localization/AppResources.lt.resx index d32abcbe7..b65502339 100644 --- a/src/Core/Resources/Localization/AppResources.lt.resx +++ b/src/Core/Resources/Localization/AppResources.lt.resx @@ -2862,4 +2862,13 @@ Ar norite pereiti prie šios paskyros? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.lv.resx b/src/Core/Resources/Localization/AppResources.lv.resx index 6fc72ee3a..38e8bb929 100644 --- a/src/Core/Resources/Localization/AppResources.lv.resx +++ b/src/Core/Resources/Localization/AppResources.lv.resx @@ -2862,4 +2862,13 @@ Vai pārslēgties uz šo kontu? Konts tika izrakstīts. + + Apvienības atļaujas tika atjauninātas, un tās pieprasa iestatīt galveno paroli. + + + Apvienība pieprasa iestatīt galveno paroli. + + + Jāuzstāda atslēgšanas iespēja, lai mainītu glabātavas noildzes darbību. + diff --git a/src/Core/Resources/Localization/AppResources.ml.resx b/src/Core/Resources/Localization/AppResources.ml.resx index 7e24023e3..5f38b7e82 100644 --- a/src/Core/Resources/Localization/AppResources.ml.resx +++ b/src/Core/Resources/Localization/AppResources.ml.resx @@ -2861,4 +2861,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.mr.resx b/src/Core/Resources/Localization/AppResources.mr.resx index 68120201f..275185ac8 100644 --- a/src/Core/Resources/Localization/AppResources.mr.resx +++ b/src/Core/Resources/Localization/AppResources.mr.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.my.resx b/src/Core/Resources/Localization/AppResources.my.resx index 7d8f4f604..47405c13b 100644 --- a/src/Core/Resources/Localization/AppResources.my.resx +++ b/src/Core/Resources/Localization/AppResources.my.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.nb.resx b/src/Core/Resources/Localization/AppResources.nb.resx index 8d71764e3..2f8dd6b56 100644 --- a/src/Core/Resources/Localization/AppResources.nb.resx +++ b/src/Core/Resources/Localization/AppResources.nb.resx @@ -946,7 +946,7 @@ Skanning skjer automatisk. Du kan ikke bruke denne funksjonen før du oppdaterer krypteringsnøkkelen din. - Encryption key migration required. Please login through the web vault to update your encryption key. + Krypteringsnøkkelmigrasjon kreves. Logg inn gjennom web-hvelvet ditt for å oppdatere krypteringsnøkkelen din. Lær mer @@ -2862,4 +2862,13 @@ Vil du bytte til denne kontoen? Konto logget ut. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ne.resx b/src/Core/Resources/Localization/AppResources.ne.resx index 7d8f4f604..47405c13b 100644 --- a/src/Core/Resources/Localization/AppResources.ne.resx +++ b/src/Core/Resources/Localization/AppResources.ne.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.nl.resx b/src/Core/Resources/Localization/AppResources.nl.resx index 5c37b5c42..bb0fb7945 100644 --- a/src/Core/Resources/Localization/AppResources.nl.resx +++ b/src/Core/Resources/Localization/AppResources.nl.resx @@ -2861,4 +2861,13 @@ Wilt u naar dit account wisselen? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen. + diff --git a/src/Core/Resources/Localization/AppResources.nn.resx b/src/Core/Resources/Localization/AppResources.nn.resx index 844feef17..6f0968396 100644 --- a/src/Core/Resources/Localization/AppResources.nn.resx +++ b/src/Core/Resources/Localization/AppResources.nn.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.or.resx b/src/Core/Resources/Localization/AppResources.or.resx index 7acd8cda4..08e4de27e 100644 --- a/src/Core/Resources/Localization/AppResources.or.resx +++ b/src/Core/Resources/Localization/AppResources.or.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.pl.resx b/src/Core/Resources/Localization/AppResources.pl.resx index 5a8e0d177..a849de211 100644 --- a/src/Core/Resources/Localization/AppResources.pl.resx +++ b/src/Core/Resources/Localization/AppResources.pl.resx @@ -2861,4 +2861,13 @@ Czy chcesz przełączyć się na to konto? Konto wylogowane. + + Uprawnienia w Twojej organizacji zostały zaktualizowane, musisz teraz ustawić hasło główne. + + + Twoja organizacja wymaga ustawienia hasła głównego. + + + Ustaw opcje odblokowania, aby zmienić czas blokowania sejfu. + diff --git a/src/Core/Resources/Localization/AppResources.pt-BR.resx b/src/Core/Resources/Localization/AppResources.pt-BR.resx index 4d57edd93..65806fd17 100644 --- a/src/Core/Resources/Localization/AppResources.pt-BR.resx +++ b/src/Core/Resources/Localization/AppResources.pt-BR.resx @@ -1116,7 +1116,7 @@ A leitura será feita automaticamente. Setembro - Número de Segurança Social + Cadastro de Pessoas Físicas (CPF) Estado / Província @@ -2800,29 +2800,29 @@ Você deseja mudar para esta conta? {0} horas - The Android Autofill Framework is used to assist in filling login information into other apps on your device. + O Framework de Preenchimento Automático do Android é usado para ajudar a preencher informações de login em outros aplicativos do seu dispositivo. - Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay. + Use o autopreenchimento interno se o teclado selecionado o suporta. Caso contrário, use a sobreposição padrão. Opções adicionais - Continue to web app? + Continuar no aplicativo web? - Continue to {0}? + Continuar para {0}? The parameter is an URL, like bitwarden.com. - Continue to Help center? + Continuar para o centro de ajuda? - Continue to contact support? + Continuar e contatar o suporte? - Continue to app store? + Continuar para a loja de apps? Make your account more secure by setting up two-step login in the Bitwarden web app. @@ -2850,7 +2850,7 @@ Você deseja mudar para esta conta? Help others find out if Bitwarden is right for them. Visit the app store and leave a rating now. - Choose the dark theme to use when your device’s dark mode is in use + Usar o tema escuro quando o modo escuro do seu dispositivo estiver ativado Criado em {0}, {1} @@ -2862,4 +2862,13 @@ Você deseja mudar para esta conta? Conta desconectada. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.pt-PT.resx b/src/Core/Resources/Localization/AppResources.pt-PT.resx index 67f1427a9..bcd1c77cb 100644 --- a/src/Core/Resources/Localization/AppResources.pt-PT.resx +++ b/src/Core/Resources/Localization/AppResources.pt-PT.resx @@ -1813,7 +1813,7 @@ A leitura será efetuada automaticamente. Política de privacidade - O Bitwarden precisa de atenção - Ative "Aparecer sobre outras" em "Serviços de preenchimento automático" nas definições do Bitwarden + O Bitwarden precisa de atenção - Ative a definição "Aparecer sobre outras" em "Serviços de preenchimento automático" nas definições do Bitwarden Serviços de preenchimento automático @@ -1831,16 +1831,16 @@ A leitura será efetuada automaticamente. Utilize o Serviço de acessibilidade do Bitwarden para preencher automaticamente as suas credenciais em aplicações e na web. Quando configurado, exibiremos um pop-up quando os campos de início de sessão forem selecionados. - Utilize o Serviço de acessibilidade do Bitwarden para preencher automaticamente as suas credenciais em aplicações e na web. (Requer que a definição Aparecer sobre outras também esteja ativada) + Utilize o Serviço de acessibilidade do Bitwarden para preencher automaticamente as suas credenciais em aplicações e na Web. (Requer que a definição "Aparecer sobre outras" também esteja ativada) - Utilize o Serviço de acessibilidade do Bitwarden para utilizar o botão de preenchimento automático do Painel instantâneo e/ou mostrar um pop-up utilizando a definição Aparecer sobre outras (se estiver ativada). + Utilize o Serviço de acessibilidade do Bitwarden para utilizar o botão de preenchimento automático do Painel instantâneo e/ou mostrar um pop-up utilizando a definição "Aparecer sobre outras" (se estiver ativada). - Necessário para utilizar o botão de preenchimento automático do Painel instantâneo, ou para aumentar o serviço de preenchimento automático utilizando a definição Aparecer sobre outras (se estiver ativada). + Necessário para utilizar o botão de preenchimento automático do Painel instantâneo, ou para aumentar o serviço de preenchimento automático utilizando a definição "Aparecer sobre outras" (se estiver ativada). - Utilizar definição Aparecer sobre outras + Utilizar a definição "Aparecer sobre outras" Permite que o Serviço de acessibilidade do Bitwarden apresente um pop-up quando os campos de início de sessão são selecionados. @@ -2694,7 +2694,7 @@ Deseja mudar para esta conta? Ação de tempo limite do cofre alterada para terminar sessão - Bloquear preenchimento automático + Bloquear o preenchimento automático O preenchimento automático não será oferecido para estes URIs. @@ -2860,4 +2860,13 @@ Deseja mudar para esta conta? Conta com sessão terminada. + + As permissões da sua organização foram atualizadas, exigindo a definição de uma palavra-passe mestra. + + + A sua organização exige a definição de uma palavra-passe mestra. + + + Configure uma opção de desbloqueio para alterar a ação de tempo limite do seu cofre. + diff --git a/src/Core/Resources/Localization/AppResources.resx b/src/Core/Resources/Localization/AppResources.resx index 0709e6934..4efdb26f7 100644 --- a/src/Core/Resources/Localization/AppResources.resx +++ b/src/Core/Resources/Localization/AppResources.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ro.resx b/src/Core/Resources/Localization/AppResources.ro.resx index 7c073f896..92baf1a70 100644 --- a/src/Core/Resources/Localization/AppResources.ro.resx +++ b/src/Core/Resources/Localization/AppResources.ro.resx @@ -2861,4 +2861,13 @@ Doriți să comutați la acest cont? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ru.resx b/src/Core/Resources/Localization/AppResources.ru.resx index a8e62b7e2..1ccd10664 100644 --- a/src/Core/Resources/Localization/AppResources.ru.resx +++ b/src/Core/Resources/Localization/AppResources.ru.resx @@ -2863,4 +2863,13 @@ Аккаунт не авторизован. + + Права доступа организации были обновлены, требуется установить мастер-пароль. + + + Необходимо установить мастер-пароль для организации. + + + Настройте опцию разблокировки для изменения действия по тайм-ауту хранилища. + diff --git a/src/Core/Resources/Localization/AppResources.si.resx b/src/Core/Resources/Localization/AppResources.si.resx index 506d7898e..e40fac610 100644 --- a/src/Core/Resources/Localization/AppResources.si.resx +++ b/src/Core/Resources/Localization/AppResources.si.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.sk.resx b/src/Core/Resources/Localization/AppResources.sk.resx index ad384c871..0570f3fb9 100644 --- a/src/Core/Resources/Localization/AppResources.sk.resx +++ b/src/Core/Resources/Localization/AppResources.sk.resx @@ -2861,4 +2861,13 @@ Chcete prepnúť na toto konto? Účet bol odhlásený. + + Povolenia vašej organizácie boli aktualizované, čo si vyžaduje nastavenie hlavného hesla. + + + Vaša organizácia vyžaduje, aby ste nastavili hlavné heslo. + + + Nastavte možnosť odomknutia, aby ste zmenili akciu pri vypršaní času trezoru. + diff --git a/src/Core/Resources/Localization/AppResources.sl.resx b/src/Core/Resources/Localization/AppResources.sl.resx index e28485b6a..4bf6e0186 100644 --- a/src/Core/Resources/Localization/AppResources.sl.resx +++ b/src/Core/Resources/Localization/AppResources.sl.resx @@ -2861,4 +2861,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.sr.resx b/src/Core/Resources/Localization/AppResources.sr.resx index beda3b812..f5eedd403 100644 --- a/src/Core/Resources/Localization/AppResources.sr.resx +++ b/src/Core/Resources/Localization/AppResources.sr.resx @@ -2863,4 +2863,13 @@ Налог је одјављен. + + Дозволе за вашу организацију су ажуриране, што захтева да поставите главну лозинку. + + + Ваша организација захтева да поставите главну лозинку. + + + Подесите опцију откључавања да бисте променили радњу временског ограничења сефа. + diff --git a/src/Core/Resources/Localization/AppResources.sv.resx b/src/Core/Resources/Localization/AppResources.sv.resx index e0ab3754d..0ac542848 100644 --- a/src/Core/Resources/Localization/AppResources.sv.resx +++ b/src/Core/Resources/Localization/AppResources.sv.resx @@ -2863,4 +2863,13 @@ Vill du byta till detta konto? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.ta.resx b/src/Core/Resources/Localization/AppResources.ta.resx index b4a046e36..8196a8bd8 100644 --- a/src/Core/Resources/Localization/AppResources.ta.resx +++ b/src/Core/Resources/Localization/AppResources.ta.resx @@ -2797,16 +2797,16 @@ {0} hours and {1} minutes - {0} hours + {0} மணிநேரம் - The Android Autofill Framework is used to assist in filling login information into other apps on your device. + தானியங்கு நிரப்புதல் சேவைகள் விளக்கம் இன்னுங்கூடுதலான. - Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay. + தானியங்கு நிரப்புதலை பயன்படுத்தவும். - Additional options + கூடுதல் தேர்வுகள் Continue to web app? @@ -2862,4 +2862,13 @@ கணக்கிலிருந்து வெளியேறப்பட்டது. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.te.resx b/src/Core/Resources/Localization/AppResources.te.resx index 7d8f4f604..47405c13b 100644 --- a/src/Core/Resources/Localization/AppResources.te.resx +++ b/src/Core/Resources/Localization/AppResources.te.resx @@ -2862,4 +2862,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.th.resx b/src/Core/Resources/Localization/AppResources.th.resx index a25aa4f63..0ca241cd6 100644 --- a/src/Core/Resources/Localization/AppResources.th.resx +++ b/src/Core/Resources/Localization/AppResources.th.resx @@ -2869,4 +2869,13 @@ Do you want to switch to this account? Account logged out. + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. + diff --git a/src/Core/Resources/Localization/AppResources.tr.resx b/src/Core/Resources/Localization/AppResources.tr.resx index 36c114d8d..2770edfeb 100644 --- a/src/Core/Resources/Localization/AppResources.tr.resx +++ b/src/Core/Resources/Localization/AppResources.tr.resx @@ -2424,7 +2424,7 @@ Kod otomatik olarak taranacaktır. "Fastmail" is the product name and should not be translated. - E-posta ilet + ForwardEmail "ForwardEmail" is the product name and should not be translated. @@ -2860,4 +2860,13 @@ Bu hesaba geçmek ister misiniz? Hesabın oturumu kapatıldı. + + Kuruluş izinleriniz güncellendi ve bir ana parola belirlemeniz gerekiyor. + + + Kuruluşunuz bir ana parola belirlemenizi gerektiriyor. + + + Kasa zaman aşımı eyleminizi değiştirmek için bir kilit açma yöntemi ayarlayın. + diff --git a/src/Core/Resources/Localization/AppResources.uk.resx b/src/Core/Resources/Localization/AppResources.uk.resx index aba606066..59d39da16 100644 --- a/src/Core/Resources/Localization/AppResources.uk.resx +++ b/src/Core/Resources/Localization/AppResources.uk.resx @@ -2861,4 +2861,13 @@ Ви вийшли з облікового запису. + + Оновлено дозволи вашої організації – вимагається встановлення головного пароля. + + + Ваша організація вимагає, щоб ви встановили головний пароль. + + + Налаштуйте спосіб розблокування, щоб змінити час очікування сховища. + diff --git a/src/Core/Resources/Localization/AppResources.vi.resx b/src/Core/Resources/Localization/AppResources.vi.resx index bb5a2ea0b..85ede1dc8 100644 --- a/src/Core/Resources/Localization/AppResources.vi.resx +++ b/src/Core/Resources/Localization/AppResources.vi.resx @@ -203,7 +203,7 @@ Title for your favorite items in the vault. - Báo lỗi + Gửi báo cáo lỗi Tạo một vấn đề tại trang GitHub của chúng tôi. @@ -279,7 +279,7 @@ Xóa tài khoản - Bạn có chắc muốn xóa tài khoản này? + Bạn có chắc muốn xóa tài khoản này không? Tài khoản này đã có rồi @@ -300,7 +300,7 @@ The title for the vault page. - Trình xác thực + Authenticator Authenticator TOTP feature @@ -371,7 +371,7 @@ Label for a username. - Trường {0} là bắt buộc. + Vui lòng nhập {0}. Validation message for when a form field is left blank and is required to be entered. @@ -394,7 +394,7 @@ Xem - Ghé thăm trang chủ + Ghé thăm trang web Trang web @@ -419,7 +419,7 @@ Sử dụng dịch vụ trợ năng của Bitwarden để tự động điền thông tin đăng nhập của bạn vào các ứng dụng và trang web. - Dịch vụ điền tự động + Dịch vụ tự động điền Tránh các ký tự dễ gây nhầm lẫn @@ -490,7 +490,7 @@ Bạn đã sẵn sàng để đăng nhập! - Dễ dàng truy cập thông tin đăng nhập của bạn từ Safari, Chrome và các ứng dụng được hỗ trợ khác. + Thông tin đăng nhập của bạn giờ đây có thể dễ dàng truy cập từ Safari, Chrome và các ứng dụng được hỗ trợ khác. Trong Safari và Chrome, tìm Bitwarden bằng cách sử dụng biểu tượng chia sẻ (gợi ý: di chuyển sang phải ở cuối cùng của trình đơn chia sẻ). @@ -544,7 +544,7 @@ 4 giờ - Lập tức + Tức thì Thời gian mở kho @@ -569,7 +569,7 @@ Mật khẩu nhập lại không trùng khớp. - Mật khẩu chính là mật khẩu bạn sử dụng để truy cập kho mật khẩu của bạn. Nó rất quan trọng nên bạn không được quên mật khẩu chủ của mình. Không thể khôi phục lại mật khẩu chính nếu bạn quên nó. + Mật khẩu chính là mật khẩu bạn sử dụng để truy cập kho mật khẩu của bạn. Nó rất quan trọng vì sẽ không có cách nào để lấy lại mật khẩu nếu bạn quên. Gợi ý mật khẩu chính (tùy chọn) @@ -578,7 +578,7 @@ Một gợi ý mật khẩu có thể giúp bạn nhớ lại mật khẩu chính của bạn nếu bạn quên nó. - Mật khẩu chính phải tối thiểu {0} kí tự. + Mật khẩu chính phải có tối thiểu {0} kí tự. Số chữ số @@ -607,7 +607,7 @@ Không có mục nào trong kho của bạn. - Không có mục nào trong kho của bạn cho trang web này. Nhấn để thêm. + Không có mục nào trong kho của bạn phù hợp với trang web/app này. Nhấn để thêm. Thông tin đăng nhập này không có tên người dùng hoặc mật khẩu được định cấu hình. @@ -806,7 +806,7 @@ Message shown when trying to launch an app that does not exist on the user's device. - Ứng dụng xác thực + Ứng dụng Authenticator For 2FA @@ -822,7 +822,7 @@ For 2FA whenever there are no available providers on this device. - Tài khoản này đã kích hoạt xác minh 2 bước, tuy nhiên dịch vụ xác minh 2 bước đã chọn không được hỗ trợ trên thiết bị này. Vui lòng sử dụng thiết bị được hỗ trợ hoặc cài đặt một ứng dụng xác thực tốt hơn. + Tài khoản này đã kích hoạt xác thực 2 bước, tuy nhiên dịch vụ xác thực hai bước đã chọn không được hỗ trợ trên thiết bị này. Vui lòng sử dụng thiết bị được hỗ trợ và/hoặc dịch vụ xác thực bổ sung có thể hoạt động tốt trên thiết bị (chẳng hạn như một ứng dụng Authenticator). Mã khôi phục @@ -907,7 +907,7 @@ Quá trình quét sẽ diễn ra tự động. Sao chép TOTP - Nếu thông tin đăng nhập có khóa xác thực, hãy sao chép mã xác minh TOTP vào bộ nhớ tạm khi bạn tự động điền thông tin đăng nhập. + Nếu thông tin đăng nhập có khóa xác thực, hãy sao chép mã xác minh TOTP khi bạn tự động điền thông tin đăng nhập. Tự động sao chép mã TOTP @@ -987,7 +987,7 @@ Quá trình quét sẽ diễn ra tự động. Chạm vào thông báo này để xem thông tin đăng nhập từ kho của bạn. - Mục tùy chỉnh + Trường tùy chỉnh Chép số @@ -996,7 +996,7 @@ Quá trình quét sẽ diễn ra tự động. Sao chép mã bảo mật - Số + Số thẻ Mã bảo mật @@ -1161,7 +1161,7 @@ Quá trình quét sẽ diễn ra tự động. Không có mục nào trong thư mục này. - Không có gì trong thùng rác. + Không có gì trong thùng rác Tự động điền bằng trợ năng @@ -1195,7 +1195,7 @@ Quá trình quét sẽ diễn ra tự động. Chúng tôi không thể mở cài đặt tự động điền cho bạn. Bạn có thể đi đến cài đặt tự động điền theo cách thủ công bằng cách vào Cài đặt > Hệ thống > Ngôn ngữ và nhập liệu > Nâng cao > Dịch vụ tự động điền. - Tên mục tùy chỉnh + Tên trường tùy chỉnh Đúng/Sai @@ -1210,7 +1210,7 @@ Quá trình quét sẽ diễn ra tự động. Văn bản - Thêm mục tùy chỉnh + Trường tùy chỉnh mới Bạn muốn thêm loại trường tùy chỉnh nào? @@ -1753,7 +1753,7 @@ Quá trình quét sẽ diễn ra tự động. Đồng bộ kho bằng cách vuốt xuống. - Đăng nhập một lần cho doanh nghiệp + Đăng nhập doanh nghiệp Đăng nhập nhanh bằng cách sử dụng cổng đăng nhập một lần của tổ chức. Vui lòng nhập thông tin nhận dạng tổ chức để bắt đầu. @@ -1801,7 +1801,7 @@ Quá trình quét sẽ diễn ra tự động. Đang tải - Bật tính năng nghĩa là bạn đồng ý các điều kiện sau: + Bạn đồng ý các điều kiện sau: @@ -2600,7 +2600,7 @@ Bạn có muốn chuyển sang tài khoản này không? Tìm kiếm một mục hoặc thêm mục mới - Không có mục nào phù hợp với tìm kiếm + Không tìm thấy Hoa Kỳ @@ -2699,7 +2699,7 @@ Bạn có muốn chuyển sang tài khoản này không? Chặn tự động điền - Tính năng tự động điền sẽ không được cung cấp cho các URI này. + Vô hiệu hóa tính năng tự động điền với các URI sau. URI bị chặn mới @@ -2800,10 +2800,10 @@ Bạn có muốn chuyển sang tài khoản này không? {0} giờ - Android Autofill Framework được dùng để hỗ trợ điền thông tin đăng nhập vào các ứng dụng khác trên thiết bị của bạn. + Dùng Android Autofill Framework để điền thông tin đăng nhập vào các ứng dụng khác trên thiết bị của bạn. - Sử dụng tính năng tự động điền nội tuyến nếu bàn phím hỗ trợ tính năng này. Nếu không, hãy sử dụng lớp phủ mặc định. + Dùng tính năng tự động điền nội tuyến nếu bàn phím hỗ trợ. Nếu không, sẽ dùng lớp phủ mặc định. Tùy chọn bổ sung @@ -2862,4 +2862,13 @@ Bạn có muốn chuyển sang tài khoản này không? Đã đăng xuất tài khoản. + + Quyền tổ chức của bạn đã được cập nhật, yêu cầu bạn đặt mật khẩu chính. + + + Tổ chức của bạn yêu cầu bạn đặt mật khẩu chính. + + + Thiết lập khóa khi hết thời gian chờ kho. + diff --git a/src/Core/Resources/Localization/AppResources.zh-Hans.resx b/src/Core/Resources/Localization/AppResources.zh-Hans.resx index 311bba392..0ea3e0822 100644 --- a/src/Core/Resources/Localization/AppResources.zh-Hans.resx +++ b/src/Core/Resources/Localization/AppResources.zh-Hans.resx @@ -273,13 +273,13 @@ The log out button text (verb). - 您确定要注销吗? + 确定要注销吗? 移除账户 - 您确定要移除此账户吗? + 确定要移除此账户吗? 账户已添加 @@ -638,7 +638,7 @@ 我们已经为您发送了包含主密码提示的邮件。 - 您确定要覆盖当前密码吗? + 确定要覆盖当前密码吗? Bitwarden 使用推送通知来自动同步您的密码库。为了获得最佳体验,接下来询问是否开启通知时,请选择「允许」。 @@ -784,7 +784,7 @@ 您想自动填充还是查看此项目? - 确定要自动填充吗?它与「{0}」并不完全匹配。 + 确定要自动填充此项目吗?它与「{0}」并不完全匹配。 匹配项目 @@ -874,7 +874,7 @@ Message shown when downloading a file - 此附件大小是 {0} 。您确定要下载到设备吗? + 此附件大小为 {0} 。确定要将其下载到您的设备上吗? The placeholder will show the file size of the attachment. Ex "25 MB" @@ -1340,7 +1340,7 @@ 所有项目 - URIs + URI Plural form of a URI @@ -1556,7 +1556,7 @@ 退出 - 您确定要退出 Bitwarden 吗? + 确定要退出 Bitwarden 吗? 当应用程序重启时,要求使用主密码解锁吗? @@ -2098,7 +2098,7 @@ {0} 正在使用客户管理加密的 SSO。继续操作将删除您的账户主密码并要求 SSO 登录。 - 如果您不想移除您的主密码,您可以退出这个组织。 + 如果您不想移除您的主密码,您可以退出该组织。 退出组织 @@ -2432,7 +2432,7 @@ API 访问令牌 - 您确定要覆盖当前用户名吗? + 确定要覆盖当前用户名吗? 生成用户名 @@ -2527,7 +2527,7 @@ 拒绝所有请求 - 您确定要拒绝所有待处理的登录请求吗? + 确定要拒绝所有待处理的登录请求吗? 请求被拒绝 @@ -2846,7 +2846,7 @@ Bitwarden 允许您使用组织与他人共享您的密码库项目。访问 bitwarden.com 网站以了解更多。 - 帮助别人了解 Bitwarden 是否适合他们。立即访问应用商店并留下评分。 + 帮助别人了解 Bitwarden 是否适合他们。立即访问 App Store 并留下评分。 当设备的深色模式启用时,选择要使用的深色主题。 @@ -2861,4 +2861,13 @@ 账户已注销。 + + 您的组织权限已更新,要求您设置主密码。 + + + 您的组织要求您设置主密码。 + + + 设置解锁选项以更改您的密码库超时操作。 + diff --git a/src/Core/Resources/Localization/AppResources.zh-Hant.resx b/src/Core/Resources/Localization/AppResources.zh-Hant.resx index 25fc29b9d..1455e4e0b 100644 --- a/src/Core/Resources/Localization/AppResources.zh-Hant.resx +++ b/src/Core/Resources/Localization/AppResources.zh-Hant.resx @@ -854,7 +854,7 @@ 若要繼續,請將您的 YubiKey 靠在裝置的背面或者將 YubiKey 插入您裝置的 USB 連接埠,然後按下按鈕。 - YubiKey 安全金鑰 + YubiKey 安全鑰匙 "YubiKey" is the product name and should not be translated. @@ -946,7 +946,7 @@ 更新加密金鑰前不能使用此功能。 - Encryption key migration required. Please login through the web vault to update your encryption key. + 需要遷移加密金鑰。請透過網頁版密碼庫登入以更新您的加密金鑰。 深入了解 @@ -1180,7 +1180,7 @@ What Apple calls their facial recognition reader. - 使用 Face ID 驗證 + 使用 Face ID 驗證。 使用 Face ID 解鎖 @@ -1678,7 +1678,7 @@ 已成功匯出密碼庫 - 複製 + 克隆 Clone an entity (verb). @@ -1741,10 +1741,10 @@ Confirmation alert message when soft-deleting a cipher. - Biometric unlock for this account is disabled pending verification of master password. + 該帳戶的生物特徵識別解鎖已被停用,等待驗證主密碼。 - Autofill biometric unlock for this account is disabled pending verification of master password. + 該帳戶的自動填入生物特徵識別解鎖已停用,等待驗證主密碼。 啟用重新整理時同步 @@ -2110,10 +2110,10 @@ FIDO2 WebAuthn - 若要繼續,請準備好 FIDO2 WebAuthn 相容的安全金鑰,在下個畫面按下 [驗證 WebAuthn],接著遵循指引。 + 若要繼續,請準備好 FIDO2 WebAuthn 相容的安全鑰匙,在下個畫面按下 [驗證 WebAuthn],接著遵循指引。 - 使用 FIDO2 WebAuthn 驗證,您可以使用外部安全金鑰進行驗證。 + 使用 FIDO2 WebAuthn 驗證,您可以使用外部安全鑰匙進行驗證。 驗證 WebAuthn @@ -2131,10 +2131,10 @@ 您的組織原則正在影響您的密碼庫逾時時間。密碼庫逾時時間最多可以設定到 {0} 小時 {1} 分鐘。 - Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s). Your vault timeout action is set to {2}. + 您的組織原則正在影響您的密碼庫逾時時間。密碼庫逾時時間最多可以設定到 {0} 小時 {1} 分鐘。您密碼庫的逾時動作被設定為 {2}。 - Your organization policies have set your vault timeout action to {0}. + 您的組織原則已將密碼庫逾時動作設定為 {0}。 您的密碼庫逾時時間超過組織設定的限制。 @@ -2488,7 +2488,7 @@ 獲取主密碼提示 - Logging in as {0} on {1} + 在 {1} 上以 {0} 身份登入 不是您嗎? @@ -2524,16 +2524,16 @@ 待處理的登入請求。 - Decline all requests + 拒絕所有要求 - Are you sure you want to decline all pending login requests? + 您確定要拒絕所有待處理的登入要求嗎? - Requests declined + 要求已拒絕 - No pending requests + 無待處理的要求 允許相機權限以使用掃描功能 @@ -2542,13 +2542,13 @@ 語言 - The language has been changed to {0}. Please restart the app to see the change + 語言已被變更為 {0}。請重啓應用程式以查看變更 - Language change requires app restart + 變更語言需要重啓應用程式 - Default (System) + 預設(系統) 重要 @@ -2584,22 +2584,22 @@ 強度不足且已暴露的主密碼 - 是密碼強度不足,且在其他資料庫中找到這個密碼。使用一個強度足夠和獨特的密碼來保護您的帳號。請問您確定要用這個密碼嗎? + 密碼強度不足,且在資料外洩事件中找到了這個密碼。使用一個強度足夠和獨特的密碼來保護您的帳戶。您確定要使用這個密碼嗎? 需要單一登入 (SSO) 組織識別碼。 - Add the key to an existing or new item + 將金輪新增到現有或新的項目 - There are no items in your vault that match "{0}" + 您的密碼庫中沒有與「{0}」一致的項目 - Search for an item or add a new item + 搜尋項目或新增項目 - There are no items that match the search + 您的密碼庫中沒有與搜尋一致的項目 美國 @@ -2608,10 +2608,10 @@ 歐盟 - Self-hosted + 自我裝載 - Data region + 資料區域 區域 @@ -2623,76 +2623,76 @@ 目前主密碼 - Logged in! + 已登入! - Approve with my other device + 使用我的其他裝置核准 - Request admin approval + 要求管理員核准 - Approve with master password + 使用主密碼核准 - Turn off using a public device + 不允許使用公用裝置 - Remember this device + 記住這個裝置 - Passkey + 密碼金鑰 - Passkeys + 密碼金鑰 應用程式 - You cannot edit passkey application because it would invalidate the passkey + 您不能編輯密碼金鑰應用程式,因為這會使密碼金鑰失效 - Passkey will not be copied + 密碼金鑰不會被複製 - The passkey will not be copied to the cloned item. Do you want to continue cloning this item? + 密碼金鑰不會被複製到克隆的項目。 您想繼續克隆該項目嗎? - Copy application + 複製應用程式 - Available for two-step login + 可用於兩步驟登入 - Master password re-prompt help + 重新詢問主密碼幫助 - Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve. + 由於記憶體不足,解鎖可能會失敗。請減少您的 KDF 記憶體設定或設定生物特徵識別解鎖來解決此問題。 - Invalid API key + 無效的 API 金輪 - Invalid API token + 無效的 API 權杖 - Admin approval requested + 已要求管理員核准 - Your request has been sent to your admin. + 您的要求已傳送給您的管理員 - You will be notified once approved. + 核准後將通知您。 - Trouble logging in? + 登入時遇到困難? - Logging in as {0} + 正以 {0} 身分登入 - Vault timeout action changed to log out + 密碼庫逾時動作已變更為登出 封鎖表單自動填入功能 @@ -2738,127 +2738,136 @@ 無法一次編輯多組 URI - Login approved + 登入已核准 - Log in with device must be set up in the settings of the Bitwarden app. Need another option? + 必須先在 Bitwarden 應用程式設定中開啟後,才可以使用裝置登入。要改用其他選項嗎? - Log in with device + 使用裝置登入 - Logging in on + 正登入到 - Vault + 密碼庫 - Appearance + 外觀 - Account security + 帳戶安全性 - Bitwarden Help Center + Bitwarden 幫助中心 - Contact Bitwarden support + 連絡 Bitwarden 支援 - Copy app information + 複製應用程式資訊 - Sync now + 立即同步 - Unlock options + 解鎖選項 - Session timeout + 工作階段逾時 - Session timeout action + 工作階段逾時動作 - Account fingerprint phrase + 帳戶指紋短語 A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing. - One hour and one minute + 1 小時 1 分鐘 - One hour and {0} minutes + 1 小時 {0} 分鐘 - {0} hours and one minute + {0} 小時 1 分鐘 - {0} hours and {1} minutes + {0} 小時 {1} 分鐘 - {0} hours + {0} 小時 - The Android Autofill Framework is used to assist in filling login information into other apps on your device. + Android 自動填入框架用於協助將登入資訊填入裝置上的其他應用程式。 - Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay. + 如果您選擇的鍵盤支持,請使用内嵌式自動填入。否則,使用預設覆蓋。 - Additional options + 額外選項 - Continue to web app? + 接下來造訪 Web App 嗎? - Continue to {0}? + 接下來造訪 {0} 嗎? The parameter is an URL, like bitwarden.com. - Continue to Help center? + 接下來造訪幫助中心嗎? - Continue to contact support? + 接下來聯絡支援嗎? - Continue to app store? + 接下來造訪 App Store 嗎? - Make your account more secure by setting up two-step login in the Bitwarden web app. + 在 Bitwarden Web 應用程式中設定兩步驟登入,讓您的帳戶更加安全。 - You can change your master password on the Bitwarden web app. + 您可以在 Bitwarden Web 應用程式上變更主密碼。 - You can import data to your vault on {0}. + 您可以將資料匯入到 {0} 上的密碼庫。 The parameter is an URL, like vault.bitwarden.com. - Learn more about how to use Bitwarden on the Help center. + 在幫助中心了解有關如何使用 Bitwarden 的更多資訊。 - Can’t find what you are looking for? Reach out to Bitwarden support on bitwarden.com. + 不能找到你想要的?請在 bitwarden.com 上聯絡 Bitwarden 支援。 - Explore more features of your Bitwarden account on the web app. + 在 Web 應用程式上探索 Bitwarden 帳戶的更多功能。 - Bitwarden allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website. + Bitwarden 可讓您透過組織與其他人共用您的密碼庫項目。請造訪 bitwarden.com 網站以了解更多資訊。 - Help others find out if Bitwarden is right for them. Visit the app store and leave a rating now. + 幫助其他人了解 Bitwarden 是否適合他們。立即造訪 App Store 並留下評分。 - Choose the dark theme to use when your device’s dark mode is in use + 選擇當您的裝置使用深色模式時要使用的深色主題 - Created {0}, {1} + 建立於 {0}, {1} To state the date/time in which the cipher was created: Created 03/21/2023, 09:25 AM. First parameter is the date and the second parameter is the time. 嘗試次數過多 - 帳號已登出。 + 帳戶已登出。 + + + Your organization permissions were updated, requiring you to set a master password. + + + Your organization requires you to set a master password. + + + Set up an unlock option to change your vault timeout action. diff --git a/src/Core/Resources/Styles/ControlTemplates.xaml b/src/Core/Resources/Styles/ControlTemplates.xaml index f61a91717..f629632a6 100644 --- a/src/Core/Resources/Styles/ControlTemplates.xaml +++ b/src/Core/Resources/Styles/ControlTemplates.xaml @@ -6,6 +6,7 @@ x:Class="Bit.App.Styles.ControlTemplates"> + diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index 93382dde6..8cabe28e0 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -9,7 +9,7 @@ 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.Data; using Bit.Core.Models.Request; using Bit.Core.Models.Response; using Bit.Core.Utilities; @@ -55,7 +55,7 @@ namespace Bit.Core.Services public string IdentityBaseUrl { get; set; } public string EventsBaseUrl { get; set; } - public void SetUrls(EnvironmentUrls urls) + public void SetUrls(EnvironmentUrlData urls) { UrlsSet = true; if (!string.IsNullOrWhiteSpace(urls.Base)) diff --git a/src/Core/Services/EmailForwarders/FastmailForwarder.cs b/src/Core/Services/EmailForwarders/FastmailForwarder.cs index a47229861..7380fd0fc 100644 --- a/src/Core/Services/EmailForwarders/FastmailForwarder.cs +++ b/src/Core/Services/EmailForwarders/FastmailForwarder.cs @@ -9,16 +9,21 @@ using Newtonsoft.Json.Linq; namespace Bit.Core.Services.EmailForwarders { - public class FastmailForwarder : BaseForwarder + public class FastmailForwarderOptions : ForwarderOptions + { + public string Website { get; set; } + } + + public class FastmailForwarder : BaseForwarder { protected override string RequestUri => "https://api.fastmail.com/jmap/api/"; - protected override void ConfigureHeaders(HttpRequestHeaders headers, ForwarderOptions options) + protected override void ConfigureHeaders(HttpRequestHeaders headers, FastmailForwarderOptions options) { headers.Add("Authorization", $"Bearer {options.ApiKey}"); } - protected override async Task GetContentAsync(IApiService apiService, ForwarderOptions options) + protected override async Task GetContentAsync(IApiService apiService, FastmailForwarderOptions options) { string accountId = null; try @@ -55,7 +60,8 @@ namespace Bit.Core.Services.EmailForwarders ["state"] = "enabled", ["description"] = "", ["url"] = "", - ["emailPrefix"] = "" + ["emailPrefix"] = "", + ["forDomain"] = options.Website ?? "" } } }, diff --git a/src/Core/Services/EnvironmentService.cs b/src/Core/Services/EnvironmentService.cs index a7a9dd197..5cbca7695 100644 --- a/src/Core/Services/EnvironmentService.cs +++ b/src/Core/Services/EnvironmentService.cs @@ -1,9 +1,7 @@ -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Bit.Core.Abstractions; +using Bit.Core.Abstractions; using Bit.Core.Models.Data; -using Bit.Core.Models.Domain; using Bit.Core.Utilities; +using BwRegion = Bit.Core.Enums.Region; namespace Bit.Core.Services { @@ -33,6 +31,7 @@ namespace Bit.Core.Services public string IconsUrl { get; set; } public string NotificationsUrl { get; set; } public string EventsUrl { get; set; } + public BwRegion SelectedRegion { get; set; } public string GetWebVaultUrl(bool returnNullIfDefault = false) { @@ -54,38 +53,33 @@ namespace Bit.Core.Services return GetWebVaultUrl(true) is string webVaultUrl ? $"{webVaultUrl}/#/send/" : DEFAULT_WEB_SEND_URL; } + public string GetCurrentDomain() + { + return new EnvironmentUrlData + { + WebVault = WebVaultUrl, + Base = BaseUrl, + Api = ApiUrl, + Identity = IdentityUrl + }.GetDomainOrHostname(); + } + public async Task SetUrlsFromStorageAsync() { try { + var region = await _stateService.GetActiveUserRegionAsync(); var urls = await _stateService.GetEnvironmentUrlsAsync(); - if (urls == null) - { - urls = await _stateService.GetPreAuthEnvironmentUrlsAsync(); - } - if (urls == null) - { - urls = new EnvironmentUrlData(); - } - var envUrls = new EnvironmentUrls(); - if (!string.IsNullOrWhiteSpace(urls.Base)) - { - BaseUrl = envUrls.Base = urls.Base; - _apiService.SetUrls(envUrls); + urls ??= await _stateService.GetPreAuthEnvironmentUrlsAsync(); + if (urls == null || urls.IsEmpty) + { + await SetRegionAsync(BwRegion.US); _conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited); return; } - BaseUrl = urls.Base; - WebVaultUrl = urls.WebVault; - ApiUrl = envUrls.Api = urls.Api; - IdentityUrl = envUrls.Identity = urls.Identity; - IconsUrl = urls.Icons; - NotificationsUrl = urls.Notifications; - EventsUrl = envUrls.Events = urls.Events; - _apiService.SetUrls(envUrls); - + await SetRegionAsync(region.Value, urls); _conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited); } catch (System.Exception ex) @@ -96,15 +90,26 @@ namespace Bit.Core.Services } - public async Task SetUrlsAsync(EnvironmentUrlData urls) + public async Task SetRegionAsync(BwRegion region, EnvironmentUrlData selfHostedUrls = null) { - urls.Base = FormatUrl(urls.Base); - urls.WebVault = FormatUrl(urls.WebVault); - urls.Api = FormatUrl(urls.Api); - urls.Identity = FormatUrl(urls.Identity); - urls.Icons = FormatUrl(urls.Icons); - urls.Notifications = FormatUrl(urls.Notifications); - urls.Events = FormatUrl(urls.Events); + EnvironmentUrlData urls; + + if (region == BwRegion.SelfHosted) + { + // If user saves a self-hosted region with empty fields, default to US + if (selfHostedUrls.IsEmpty) + { + return await SetRegionAsync(BwRegion.US); + } + urls = selfHostedUrls.FormatUrls(); + } + else + { + urls = region.GetUrls(); + } + + SelectedRegion = region; + await _stateService.SetPreAuthRegionAsync(region); await _stateService.SetPreAuthEnvironmentUrlsAsync(urls); BaseUrl = urls.Base; WebVaultUrl = urls.WebVault; @@ -113,35 +118,8 @@ namespace Bit.Core.Services IconsUrl = urls.Icons; NotificationsUrl = urls.Notifications; EventsUrl = urls.Events; - - var envUrls = new EnvironmentUrls(); - if (!string.IsNullOrWhiteSpace(BaseUrl)) - { - envUrls.Base = BaseUrl; - } - else - { - envUrls.Api = ApiUrl; - envUrls.Identity = IdentityUrl; - envUrls.Events = EventsUrl; - } - - _apiService.SetUrls(envUrls); + _apiService.SetUrls(urls); return urls; } - - private string FormatUrl(string url) - { - if (string.IsNullOrWhiteSpace(url)) - { - return null; - } - url = Regex.Replace(url, "\\/+$", string.Empty); - if (!url.StartsWith("http://") && !url.StartsWith("https://")) - { - url = string.Concat("https://", url); - } - return url.Trim(); - } } } diff --git a/src/Core/Services/StateMigrationService.cs b/src/Core/Services/StateMigrationService.cs index 933205dd9..360c69e3b 100644 --- a/src/Core/Services/StateMigrationService.cs +++ b/src/Core/Services/StateMigrationService.cs @@ -1,20 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Bit.Core.Abstractions; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Utilities; using DeviceType = Bit.Core.Enums.DeviceType; +using Region = Bit.Core.Enums.Region; namespace Bit.Core.Services { public class StateMigrationService : IStateMigrationService { - private const int StateVersion = 6; + private const int StateVersion = 7; private readonly DeviceType _deviceType; private readonly IStorageService _preferencesStorageService; @@ -87,6 +83,9 @@ namespace Bit.Core.Services goto case 5; case 5: await MigrateFrom5To6Async(); + goto case 6; + case 6: + await MigrateFrom6To7Async(); break; } } @@ -838,6 +837,42 @@ namespace Bit.Core.Services #endregion + #region v6 to v7 Migration + + private class V7Keys + { + // global keys + internal const string StateKey = "state"; + internal const string RegionEnvironmentKey = "regionEnvironment"; + internal const string PreAuthEnvironmentUrlsKey = "preAuthEnvironmentUrls"; + } + + private async Task MigrateFrom6To7Async() + { + // account data + var state = await GetValueAsync(Storage.Prefs, V7Keys.StateKey); + + // Migrate environment data to use Regions + foreach (var account in state.Accounts.Where(a => a.Value?.Profile?.UserId != null && a.Value?.Settings != null)) + { + var urls = account.Value.Settings.EnvironmentUrls ?? Region.US.GetUrls(); + account.Value.Settings.Region = urls.Region; + account.Value.Settings.EnvironmentUrls = urls.Region.GetUrls() ?? urls; + } + + await SetValueAsync(Storage.Prefs, Constants.StateKey, state); + + // Update pre auth urls and region + var preAuthUrls = await GetValueAsync(Storage.Prefs, V7Keys.PreAuthEnvironmentUrlsKey) ?? Region.US.GetUrls(); + await SetValueAsync(Storage.Prefs, V7Keys.RegionEnvironmentKey, preAuthUrls.Region); + await SetValueAsync(Storage.Prefs, V7Keys.PreAuthEnvironmentUrlsKey, preAuthUrls.Region.GetUrls() ?? preAuthUrls); + + + // Update stored version + await SetLastStateVersionAsync(7); + } + #endregion + // Helpers private async Task GetLastStateVersionAsync() diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 6f2289a43..e8c997f18 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Bit.Core.Abstractions; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; using Bit.Core.Models.View; using Bit.Core.Utilities; +using BwRegion = Bit.Core.Enums.Region; namespace Bit.Core.Services { @@ -1363,6 +1360,30 @@ namespace Bit.Core.Services _storageMediatorService.Save(Constants.ConfigsKey, value); } + public async Task SetUserHasMasterPasswordAsync(bool value, string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var account = await GetAccountAsync(reconciledOptions); + account.Profile.UserDecryptionOptions.HasMasterPassword = value; + await SaveAccountAsync(account, reconciledOptions); + } + + public async Task GetActiveUserRegionAsync() + { + return await GetActiveUserCustomDataAsync(a => a?.Settings?.Region); + } + + public async Task GetPreAuthRegionAsync() + { + return await _storageMediatorService.GetAsync(Constants.RegionEnvironment); + } + + public async Task SetPreAuthRegionAsync(BwRegion value) + { + await _storageMediatorService.SaveAsync(Constants.RegionEnvironment, value); + } + // Helpers [Obsolete("Use IStorageMediatorService instead")] @@ -1552,6 +1573,7 @@ namespace Bit.Core.Services await CheckStateAsync(); account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); + account.Settings.Region = await GetPreAuthRegionAsync(); // Storage var state = await GetStateFromStorageAsync() ?? new State(); diff --git a/src/Core/Services/SyncService.cs b/src/Core/Services/SyncService.cs index 5a2bf1a8a..6608a811c 100644 --- a/src/Core/Services/SyncService.cs +++ b/src/Core/Services/SyncService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Models.Data; +using Bit.Core.Models.Domain; using Bit.Core.Models.Response; using Bit.Core.Utilities; @@ -398,6 +399,33 @@ namespace Bit.Core.Services await _stateService.SetPersonalPremiumAsync(response.Premium); await _stateService.SetAvatarColorAsync(response.AvatarColor); await _keyConnectorService.SetUsesKeyConnectorAsync(response.UsesKeyConnector); + await SetPasswordSetReasonIfNeededAsync(response); + } + + private async Task SetPasswordSetReasonIfNeededAsync(ProfileResponse response) + { + // The `ForcePasswordReset` flag indicates an admin has reset the user's password and must be updated + if (response.ForcePasswordReset) + { + await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.AdminForcePasswordReset); + } + + var hasManageResetPasswordPermission = response.Organizations.Any(org => + org.Type == Enums.OrganizationUserType.Owner || + org.Type == Enums.OrganizationUserType.Admin || + org.Permissions?.ManageResetPassword == true); + if (!hasManageResetPasswordPermission) + { + return; + } + + var decryptionOptions = await _stateService.GetAccountDecryptionOptions(); + if (decryptionOptions?.HasMasterPassword == false) + { + // TDE user w/out MP went from having no password reset permission to having it. + // Must set the force password reset reason so the auth guard will redirect to the set password page. + await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission); + } } private async Task SyncFoldersAsync(string userId, List response) diff --git a/src/Core/Services/UsernameGenerationService.cs b/src/Core/Services/UsernameGenerationService.cs index 5789dac5c..2ed5454fe 100644 --- a/src/Core/Services/UsernameGenerationService.cs +++ b/src/Core/Services/UsernameGenerationService.cs @@ -148,6 +148,13 @@ namespace Bit.Core.Services .GenerateAsync(_apiService, forwardedEmailOptions); } + if (options.ServiceType == ForwardedEmailServiceType.Fastmail) + { + var fastmailEmailOptions = (FastmailForwarderOptions)options.GetForwarderOptions(); + return await new FastmailForwarder() + .GenerateAsync(_apiService, fastmailEmailOptions); + } + BaseForwarder simpleForwarder = null; switch (options.ServiceType) @@ -161,9 +168,6 @@ namespace Bit.Core.Services case ForwardedEmailServiceType.DuckDuckGo: simpleForwarder = new DuckDuckGoForwarder(); break; - case ForwardedEmailServiceType.Fastmail: - simpleForwarder = new FastmailForwarder(); - break; default: _logger.Value.Error($"Error UsernameGenerationService: ForwardedEmailServiceType {options.ServiceType} not implemented."); return Constants.DefaultUsernameGenerated; diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index b5c985095..d9ee8fd44 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -63,13 +63,6 @@ namespace Bit.Core.Services /// public async Task IsLockedAsync(string userId = null) { - // If biometrics are used, we can use the flag to determine locked state - var biometricSet = await IsBiometricLockSetAsync(userId); - if (biometricSet && await _stateService.GetBiometricLockedAsync(userId)) - { - return true; - } - if (!await _cryptoService.HasUserKeyAsync(userId)) { try diff --git a/src/Core/Utilities/AppHelpers.cs b/src/Core/Utilities/AppHelpers.cs index 2ff27e546..07db2fb81 100644 --- a/src/Core/Utilities/AppHelpers.cs +++ b/src/Core/Utilities/AppHelpers.cs @@ -407,14 +407,15 @@ namespace Bit.App.Utilities var settingValue = string.IsNullOrWhiteSpace(setting.Value) ? null : setting.Value; if (environmentService.BaseUrl != settingValue) { - await environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData + var urls = new EnvironmentUrlData { Base = settingValue, Api = environmentService.ApiUrl, Identity = environmentService.IdentityUrl, WebVault = environmentService.WebVaultUrl, Icons = environmentService.IconsUrl - }); + }; + await environmentService.SetRegionAsync(urls.Region, urls); } return; default: diff --git a/src/Core/Utilities/AsyncCommand.cs b/src/Core/Utilities/AsyncCommand.cs deleted file mode 100644 index cc81ed0b2..000000000 --- a/src/Core/Utilities/AsyncCommand.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Windows.Input; -using CommunityToolkit.Mvvm.Input; - -namespace Bit.App.Utilities -{ - // TODO: [MAUI-Migration] DELETE WHEN MIGRATION IS DONE - /// - - /// Wrapper of just to ease with the MAUI migration process. - /// After the process is done, remove this and use AsyncRelayCommand directly - /// - public class AsyncCommand : ICommand - { - readonly AsyncRelayCommand _relayCommand; - - public AsyncCommand(Func execute, Func canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) - { - async Task doAsync() - { - try - { - await execute?.Invoke(); - } - catch (Exception ex) - { - onException?.Invoke(ex); - } - } - - var safeCanExecute = canExecute; - if (canExecute is null) - { - safeCanExecute = () => true; - } - _relayCommand = new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); - } - - public event EventHandler CanExecuteChanged; - - public bool CanExecute(object parameter) => _relayCommand.CanExecute(parameter); - public void Execute(object parameter) => _relayCommand.Execute(parameter); - public void RaiseCanExecuteChanged() => _relayCommand.NotifyCanExecuteChanged(); - } - - /// Wrapper of just to ease with the MAUI migration process. - /// After the process is done, remove this and use AsyncRelayCommand directly - /// - public class AsyncCommand : ICommand - { - readonly AsyncRelayCommand _relayCommand; - - public AsyncCommand(Func execute, Predicate canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) - { - async Task doAsync(T foo) - { - try - { - await execute?.Invoke(foo); - } - catch (Exception ex) - { - onException?.Invoke(ex); - } - } - - var safeCanExecute = canExecute; - if (canExecute is null) - { - safeCanExecute = _ => true; - } - _relayCommand = new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); - } - - public event EventHandler CanExecuteChanged; - - public bool CanExecute(object parameter) => _relayCommand.CanExecute(parameter); - public void Execute(object parameter) => _relayCommand.Execute(parameter); - public void RaiseCanExecuteChanged() => _relayCommand.NotifyCanExecuteChanged(); - } -} diff --git a/src/Core/Utilities/BoolToColorConverter.cs b/src/Core/Utilities/BoolToColorConverter.cs new file mode 100644 index 000000000..d523924f2 --- /dev/null +++ b/src/Core/Utilities/BoolToColorConverter.cs @@ -0,0 +1,22 @@ +namespace Bit.App.Utilities +{ + public class BoolEnablementToTextColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + if (targetType == typeof(Color) && value is bool valueBool) + { + return valueBool ? ThemeManager.GetResourceColor("TextColor") : + ThemeManager.GetResourceColor("MutedColor"); + } + throw new InvalidOperationException("The value must be a boolean with a Color target."); + } + + public object ConvertBack(object value, Type targetType, object parameter, + System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Core/Utilities/ExtendedViewModel.cs b/src/Core/Utilities/ExtendedViewModel.cs index ed586d453..6251a5543 100644 --- a/src/Core/Utilities/ExtendedViewModel.cs +++ b/src/Core/Utilities/ExtendedViewModel.cs @@ -1,14 +1,96 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.CompilerServices; +using Bit.App.Abstractions; +using Bit.Core.Abstractions; +using Bit.Core.Exceptions; +using Bit.Core.Resources.Localization; +using CommunityToolkit.Mvvm.Input; namespace Bit.Core.Utilities { public abstract class ExtendedViewModel : INotifyPropertyChanged { + protected LazyResolve _deviceActionService = new LazyResolve(); + protected LazyResolve _platformUtilsService = new LazyResolve(); + protected LazyResolve _logger = new LazyResolve(); + public event PropertyChangedEventHandler PropertyChanged; + protected AsyncRelayCommand CreateDefaultAsyncRelayCommand(Func execute, Func canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) + { + var safeCanExecute = canExecute; + if (canExecute is null) + { + safeCanExecute = () => true; + } + + async Task doAsync() + { + try + { + await execute?.Invoke(); + } + catch (Exception ex) + { + if (onException != null) + { + onException(ex); + } + else + { + HandleException(ex); + } + } + } + + return new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); + } + + protected AsyncRelayCommand CreateDefaultAsyncRelayCommand(Func execute, Predicate canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) + { + var safeCanExecute = canExecute; + if (canExecute is null) + { + safeCanExecute = _ => true; + } + + async Task doAsync(T foo) + { + try + { + await execute?.Invoke(foo); + } + catch (Exception ex) + { + if (onException != null) + { + onException(ex); + } + else + { + HandleException(ex); + } + } + } + + return new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); + } + + protected void HandleException(Exception ex, string message = null) + { + if (ex is ApiException apiException && apiException.Error != null) + { + message = apiException.Error.GetSingleMessage(); + } + + Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () => + { + await _deviceActionService.Value.HideLoadingAsync(); + await _platformUtilsService.Value.ShowDialogAsync(message ?? AppResources.GenericErrorMessage); + }).FireAndForget(); + _logger.Value.Exception(ex); + } + protected bool SetProperty(ref T backingStore, T value, Action onChanged = null, [CallerMemberName] string propertyName = "", string[] additionalPropertyNames = null) { diff --git a/src/Core/Utilities/RegionExtensions.cs b/src/Core/Utilities/RegionExtensions.cs new file mode 100644 index 000000000..ecead2a70 --- /dev/null +++ b/src/Core/Utilities/RegionExtensions.cs @@ -0,0 +1,49 @@ +using Bit.Core.Models.Data; +using BwRegion = Bit.Core.Enums.Region; + +namespace Bit.Core.Utilities +{ + public static class RegionExtensions + { + public static EnvironmentUrlData GetUrls(this BwRegion region) + { + switch (region) + { + case BwRegion.US: + return EnvironmentUrlData.DefaultUS; + case BwRegion.EU: + return EnvironmentUrlData.DefaultEU; + default: + return null; + } + } + + public static string BaseUrl(this BwRegion region) + { + switch (region) + { + case BwRegion.US: + return EnvironmentUrlData.DefaultUS.Base; + case BwRegion.EU: + return EnvironmentUrlData.DefaultEU.Base; + default: + return null; + } + } + + public static string Domain(this BwRegion region) + { + switch (region) + { + case BwRegion.US: + return EnvironmentUrlData.DefaultUS.Domain; + case BwRegion.EU: + return EnvironmentUrlData.DefaultEU.Domain; + default: + return null; + } + } + + } +} + diff --git a/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/fi.lproj/Localizable.strings b/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/fi.lproj/Localizable.strings index 954f8395c..45c1d9210 100644 --- a/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/fi.lproj/Localizable.strings +++ b/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/fi.lproj/Localizable.strings @@ -7,4 +7,4 @@ "SetUpBitwardenToViewItemsContainingVerificationCodes" = "Määritä Bitwarden nähdäksesi vahvistuskoodeja sisältävät kohteet."; "Search" = "Haku"; "NoItemsFound" = "Kohteita ei löytynyt."; -"SetUpAppleWatchPasscodeInOrderToUseBitwarden" = "Määritä Apple Watch- lukituskoodi Bitwardenn käyttämiseksi."; +"SetUpAppleWatchPasscodeInOrderToUseBitwarden" = "Määritä Apple Watch -lukituskoodi Bitwardenin käyttämiseksi."; diff --git a/store/apple/it/copy.resx b/store/apple/it/copy.resx index 51d47ae7a..e641704c0 100644 --- a/store/apple/it/copy.resx +++ b/store/apple/it/copy.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gestore di Password + Bitwarden Password Manager Max 30 characters @@ -151,7 +151,7 @@ Proteggi e condividi i dati sensibili all'interno della tua cassaforte di Bitwar Max 4000 characters - bit warden,8bit,password,gestore password gratis,gestore password,gestore login + bit warden,8bit,password,gestore password gratis,gestore password,gestore login,password manager,password manager gratis Max 100 characters diff --git a/store/google/it/copy.resx b/store/google/it/copy.resx index bb1675d94..ce42f8f52 100644 --- a/store/google/it/copy.resx +++ b/store/google/it/copy.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden - Gestore di Password + Bitwarden Password Manager Max 30 characters @@ -155,7 +155,7 @@ Proteggi e condividi i dati sensibili all'interno della tua cassaforte di Bitwar Max 4000 characters - Un gestore di password sicuro e gratis per tutti i tuoi dispositivi + Un password manager sicuro e gratis per tutti i tuoi dispositivi Gestisci tutte le tue password e i tuoi login da una cassaforte sicura