diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs index 32a73d2cf..294714826 100644 --- a/src/Android/Autofill/AutofillService.cs +++ b/src/Android/Autofill/AutofillService.cs @@ -23,6 +23,8 @@ namespace Bit.Droid.Autofill private ICipherService _cipherService; private IVaultTimeoutService _vaultTimeoutService; private IStorageService _storageService; + private IPolicyService _policyService; + private IUserService _userService; public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) @@ -89,6 +91,26 @@ namespace Bit.Droid.Autofill return; } + _policyService ??= ServiceContainer.Resolve("policyService"); + + var personalOwnershipPolicies = await _policyService.GetAll(PolicyType.PersonalOwnership); + if (personalOwnershipPolicies != null) + { + _userService ??= ServiceContainer.Resolve("userService"); + foreach (var policy in personalOwnershipPolicies) + { + if (policy.Enabled) + { + var org = await _userService.GetOrganizationAsync(policy.OrganizationId); + if (org != null && org.Enabled && org.UsePolicies && !org.IsAdmin + && org.Status == OrganizationUserStatusType.Confirmed) + { + return; + } + } + } + } + var parser = new Parser(structure, ApplicationContext); parser.Parse(); diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/AddEditPageViewModel.cs index 34a44b04c..0852a5bbf 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/AddEditPageViewModel.cs @@ -25,6 +25,7 @@ namespace Bit.App.Pages private readonly IAuditService _auditService; private readonly IMessagingService _messagingService; private readonly IEventService _eventService; + private readonly IPolicyService _policyService; private CipherView _cipher; private bool _showNotesSeparator; private bool _showPassword; @@ -78,6 +79,7 @@ namespace Bit.App.Pages _messagingService = ServiceContainer.Resolve("messagingService"); _collectionService = ServiceContainer.Resolve("collectionService"); _eventService = ServiceContainer.Resolve("eventService"); + _policyService = ServiceContainer.Resolve("policyService"); GeneratePasswordCommand = new Command(GeneratePassword); TogglePasswordCommand = new Command(TogglePassword); ToggleCardCodeCommand = new Command(ToggleCardCode); @@ -87,6 +89,7 @@ namespace Bit.App.Pages Uris = new ExtendedObservableCollection(); Fields = new ExtendedObservableCollection(); Collections = new ExtendedObservableCollection(); + AllowPersonal = true; TypeOptions = new List> { @@ -276,6 +279,7 @@ namespace Bit.App.Pages public string ShowCardCodeIcon => ShowCardCode ? "" : ""; public int PasswordFieldColSpan => Cipher.ViewPassword ? 1 : 4; public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2; + public bool AllowPersonal { get; set; } public void Init() { @@ -284,6 +288,7 @@ namespace Bit.App.Pages public async Task LoadAsync(AppOptions appOptions = null) { + var policies = (await _policyService.GetAll(PolicyType.PersonalOwnership))?.ToList(); var myEmail = await _userService.GetEmailAsync(); OwnershipOptions.Add(new KeyValuePair(myEmail, null)); var orgs = await _userService.GetAllOrganizationAsync(); @@ -292,6 +297,24 @@ namespace Bit.App.Pages if (org.Enabled && org.Status == OrganizationUserStatusType.Confirmed) { OwnershipOptions.Add(new KeyValuePair(org.Name, org.Id)); + if (policies != null && org.UsePolicies && !org.IsAdmin && AllowPersonal) + { + foreach (var policy in policies) + { + if (policy.OrganizationId == org.Id && policy.Enabled) + { + AllowPersonal = false; + // Remove personal ownership + OwnershipOptions.RemoveAt(0); + // Default to the organization who owns this policy for now (if necessary) + if (string.IsNullOrWhiteSpace(OrganizationId)) + { + OrganizationId = org.Id; + } + break; + } + } + } } } @@ -364,6 +387,12 @@ namespace Bit.App.Pages IdentityTitleOptions.FindIndex(k => k.Value == Cipher.Identity.Title); OwnershipSelectedIndex = string.IsNullOrWhiteSpace(Cipher.OrganizationId) ? 0 : OwnershipOptions.FindIndex(k => k.Value == Cipher.OrganizationId); + + // If the selected organization is on Index 0 and we've removed the personal option, force refresh + if (!AllowPersonal && OwnershipSelectedIndex == 0) + { + OrganizationChanged(); + } if ((!EditMode || CloneMode) && (CollectionIds?.Any() ?? false)) { @@ -410,6 +439,13 @@ namespace Bit.App.Pages AppResources.Ok); return false; } + + if ((!EditMode || CloneMode) && !AllowPersonal && string.IsNullOrWhiteSpace(Cipher.OrganizationId)) + { + await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + AppResources.PersonalOwnershipSubmitError,AppResources.Ok); + return false; + } Cipher.Fields = Fields != null && Fields.Any() ? Fields.Where(f => f != null).Select(f => f.Field).ToList() : null; diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 00bc055c6..82325d809 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -3158,5 +3158,11 @@ namespace Bit.App.Resources { return ResourceManager.GetString("DrawOverDescription3", resourceCulture); } } + + public static string PersonalOwnershipSubmitError { + get { + return ResourceManager.GetString("PersonalOwnershipSubmitError", resourceCulture); + } + } } } diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 48de532c8..2e18bc28f 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1786,4 +1786,7 @@ If enabled, accessibility will show a popup to augment the Autofill Service for older apps that don't support the Android Autofill Framework. + + Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections. + \ No newline at end of file diff --git a/src/Core/Enums/PolicyType.cs b/src/Core/Enums/PolicyType.cs index 1fc48e59b..31d170d25 100644 --- a/src/Core/Enums/PolicyType.cs +++ b/src/Core/Enums/PolicyType.cs @@ -6,6 +6,7 @@ MasterPassword = 1, PasswordGenerator = 2, OnlyOrg = 3, - RequireSso = 4 + RequireSso = 4, + PersonalOwnership = 5, } } diff --git a/src/Core/Models/Data/OrganizationData.cs b/src/Core/Models/Data/OrganizationData.cs index 4d1621fb2..65140e6d1 100644 --- a/src/Core/Models/Data/OrganizationData.cs +++ b/src/Core/Models/Data/OrganizationData.cs @@ -20,6 +20,7 @@ namespace Bit.Core.Models.Data UseTotp = response.UseTotp; Use2fa = response.Use2fa; UseApi = response.UseApi; + UsePolicies = response.UsePolicies; SelfHost = response.SelfHost; UsersGetPremium = response.UsersGetPremium; Seats = response.Seats; @@ -38,6 +39,7 @@ namespace Bit.Core.Models.Data public bool UseTotp { get; set; } public bool Use2fa { get; set; } public bool UseApi { get; set; } + public bool UsePolicies { get; set; } public bool SelfHost { get; set; } public bool UsersGetPremium { get; set; } public int Seats { get; set; } diff --git a/src/Core/Models/Domain/Organization.cs b/src/Core/Models/Domain/Organization.cs index 96a807888..aef97622e 100644 --- a/src/Core/Models/Domain/Organization.cs +++ b/src/Core/Models/Domain/Organization.cs @@ -20,6 +20,7 @@ namespace Bit.Core.Models.Domain UseTotp = obj.UseTotp; Use2fa = obj.Use2fa; UseApi = obj.UseApi; + UsePolicies = obj.UsePolicies; SelfHost = obj.SelfHost; UsersGetPremium = obj.UsersGetPremium; Seats = obj.Seats; @@ -38,6 +39,7 @@ namespace Bit.Core.Models.Domain public bool UseTotp { get; set; } public bool Use2fa { get; set; } public bool UseApi { get; set; } + public bool UsePolicies { get; set; } public bool SelfHost { get; set; } public bool UsersGetPremium { get; set; } public int Seats { get; set; } diff --git a/src/Core/Models/Response/ProfileOrganizationResponse.cs b/src/Core/Models/Response/ProfileOrganizationResponse.cs index c1b6b8826..d6ca8dcef 100644 --- a/src/Core/Models/Response/ProfileOrganizationResponse.cs +++ b/src/Core/Models/Response/ProfileOrganizationResponse.cs @@ -12,6 +12,7 @@ namespace Bit.Core.Models.Response public bool UseTotp { get; set; } public bool Use2fa { get; set; } public bool UseApi { get; set; } + public bool UsePolicies { get; set; } public bool UsersGetPremium { get; set; } public bool SelfHost { get; set; } public int Seats { get; set; }