From 70f5fd50303c82d2ff0cd2d7170beaa0a34f32f7 Mon Sep 17 00:00:00 2001
From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Date: Fri, 11 Dec 2020 10:45:26 -0600
Subject: [PATCH] [Policy] Personal Ownership (#1013)
* Initial commit of disable personal vault policy
* Added new sproc // updated policy check (was missing conditionals)
* Updated DeMorgan's law logic
---
.../src/Portal/Models/PolicyEditModel.cs | 1 +
.../src/Portal/Models/PolicyModel.cs | 5 ++++
.../src/Portal/Views/Policies/Edit.cshtml | 11 +++++++++
src/Core/Enums/PolicyType.cs | 1 +
.../IOrganizationUserRepository.cs | 2 ++
.../SqlServer/OrganizationUserRepository.cs | 14 +++++++++++
src/Core/Resources/SharedResources.en.resx | 9 +++++++
.../Services/Implementations/CipherService.cs | 18 ++++++++++++++
src/Sql/Sql.sqlproj | 1 +
...tails_ReadByUserIdStatusOrganizationId.sql | 17 +++++++++++++
...> 2020-12-04_00_OrgUserReadByOrgEmail.sql} | 0
...gUserOrgDetailsReadByUserIdStatusOrgId.sql | 24 +++++++++++++++++++
12 files changed, 103 insertions(+)
create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId.sql
rename util/Migrator/DbScripts/{2020_12_04_00_OrgUserReadByOrgEmail.sql => 2020-12-04_00_OrgUserReadByOrgEmail.sql} (100%)
create mode 100644 util/Migrator/DbScripts/2020-12-06_00_OrgUserOrgDetailsReadByUserIdStatusOrgId.sql
diff --git a/bitwarden_license/src/Portal/Models/PolicyEditModel.cs b/bitwarden_license/src/Portal/Models/PolicyEditModel.cs
index 0ed75eab9e..4d2cf14a5d 100644
--- a/bitwarden_license/src/Portal/Models/PolicyEditModel.cs
+++ b/bitwarden_license/src/Portal/Models/PolicyEditModel.cs
@@ -84,6 +84,7 @@ namespace Bit.Portal.Models
case PolicyType.SingleOrg:
case PolicyType.TwoFactorAuthentication:
case PolicyType.RequireSso:
+ case PolicyType.PersonalOwnership:
break;
default:
throw new ArgumentOutOfRangeException();
diff --git a/bitwarden_license/src/Portal/Models/PolicyModel.cs b/bitwarden_license/src/Portal/Models/PolicyModel.cs
index 057ec18c04..1c365148ab 100644
--- a/bitwarden_license/src/Portal/Models/PolicyModel.cs
+++ b/bitwarden_license/src/Portal/Models/PolicyModel.cs
@@ -41,6 +41,11 @@ namespace Bit.Portal.Models
NameKey = "RequireSso";
DescriptionKey = "RequireSsoDescription";
break;
+
+ case PolicyType.PersonalOwnership:
+ NameKey = "PersonalOwnership";
+ DescriptionKey = "PersonalOwnershipDescription";
+ break;
default:
throw new ArgumentOutOfRangeException();
diff --git a/bitwarden_license/src/Portal/Views/Policies/Edit.cshtml b/bitwarden_license/src/Portal/Views/Policies/Edit.cshtml
index ac1c1488ba..203e22c7a0 100644
--- a/bitwarden_license/src/Portal/Views/Policies/Edit.cshtml
+++ b/bitwarden_license/src/Portal/Views/Policies/Edit.cshtml
@@ -51,6 +51,17 @@
}
+ @if (Model.PolicyType == PolicyType.PersonalOwnership)
+ {
+
+
+
+ @i18nService.T("Warning")
+
+ @i18nService.T("PersonalOwnershipExemption")
+
+ }
+
diff --git a/src/Core/Enums/PolicyType.cs b/src/Core/Enums/PolicyType.cs
index bfd9514f97..e939c385f2 100644
--- a/src/Core/Enums/PolicyType.cs
+++ b/src/Core/Enums/PolicyType.cs
@@ -7,5 +7,6 @@
PasswordGenerator = 2,
SingleOrg = 3,
RequireSso = 4,
+ PersonalOwnership = 5,
}
}
diff --git a/src/Core/Repositories/IOrganizationUserRepository.cs b/src/Core/Repositories/IOrganizationUserRepository.cs
index 92dcf595dc..04a87fffb4 100644
--- a/src/Core/Repositories/IOrganizationUserRepository.cs
+++ b/src/Core/Repositories/IOrganizationUserRepository.cs
@@ -23,6 +23,8 @@ namespace Bit.Core.Repositories
Task> GetManyDetailsByOrganizationAsync(Guid organizationId);
Task> GetManyDetailsByUserAsync(Guid userId,
OrganizationUserStatusType? status = null);
+ Task GetDetailsByUserAsync(Guid userId, Guid organizationId,
+ OrganizationUserStatusType? status = null);
Task UpdateGroupsAsync(Guid orgUserId, IEnumerable groupIds);
Task CreateAsync(OrganizationUser obj, IEnumerable collections);
Task ReplaceAsync(OrganizationUser obj, IEnumerable collections);
diff --git a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs
index 76407cc7d5..1fa94027af 100644
--- a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs
+++ b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs
@@ -184,6 +184,20 @@ namespace Bit.Core.Repositories.SqlServer
return results.ToList();
}
}
+
+ public async Task GetDetailsByUserAsync(Guid userId,
+ Guid organizationId, OrganizationUserStatusType? status = null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var results = await connection.QueryAsync(
+ "[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]",
+ new { UserId = userId, Status = status, OrganizationId = organizationId },
+ commandType: CommandType.StoredProcedure);
+
+ return results.SingleOrDefault();
+ }
+ }
public async Task UpdateGroupsAsync(Guid orgUserId, IEnumerable groupIds)
{
diff --git a/src/Core/Resources/SharedResources.en.resx b/src/Core/Resources/SharedResources.en.resx
index 56ff3d83bb..d020ab9851 100644
--- a/src/Core/Resources/SharedResources.en.resx
+++ b/src/Core/Resources/SharedResources.en.resx
@@ -566,4 +566,13 @@
Organization Owners and Administrators are exempt from this policy's enforcement.
+
+ Personal Ownership
+
+
+ Require users to save vault items to an organization by removing the personal ownership option.
+
+
+ Organization Owners and Administrators are exempt from this policy's enforcement.
+
diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs
index ce8319364a..4307fa13cc 100644
--- a/src/Core/Services/Implementations/CipherService.cs
+++ b/src/Core/Services/Implementations/CipherService.cs
@@ -27,6 +27,7 @@ namespace Bit.Core.Services
private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IEventService _eventService;
private readonly IUserService _userService;
+ private readonly IPolicyRepository _policyRepository;
private readonly GlobalSettings _globalSettings;
public CipherService(
@@ -41,6 +42,7 @@ namespace Bit.Core.Services
IAttachmentStorageService attachmentStorageService,
IEventService eventService,
IUserService userService,
+ IPolicyRepository policyRepository,
GlobalSettings globalSettings)
{
_cipherRepository = cipherRepository;
@@ -54,6 +56,7 @@ namespace Bit.Core.Services
_attachmentStorageService = attachmentStorageService;
_eventService = eventService;
_userService = userService;
+ _policyRepository = policyRepository;
_globalSettings = globalSettings;
}
@@ -118,6 +121,21 @@ namespace Bit.Core.Services
}
else
{
+ // Make sure the user can save new ciphers to their personal vault
+ var userPolicies = await _policyRepository.GetManyByUserIdAsync(savingUserId);
+ if (userPolicies != null)
+ {
+ foreach (var policy in userPolicies.Where(p => p.Enabled && p.Type == PolicyType.PersonalOwnership))
+ {
+ var org = await _organizationUserRepository.GetDetailsByUserAsync(savingUserId, policy.OrganizationId,
+ OrganizationUserStatusType.Confirmed);
+ if(org != null && org.Enabled && org.UsePolicies
+ && org.Type != OrganizationUserType.Admin && org.Type != OrganizationUserType.Owner)
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.");
+ }
+ }
+ }
await _cipherRepository.CreateAsync(cipher);
}
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created);
diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj
index 9d500ae401..70b9b3edf9 100644
--- a/src/Sql/Sql.sqlproj
+++ b/src/Sql/Sql.sqlproj
@@ -295,6 +295,7 @@
+
diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId.sql b/src/Sql/dbo/Stored Procedures/OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId.sql
new file mode 100644
index 0000000000..a3e15d78e0
--- /dev/null
+++ b/src/Sql/dbo/Stored Procedures/OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId.sql
@@ -0,0 +1,17 @@
+CREATE PROCEDURE [dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]
+ @UserId UNIQUEIDENTIFIER,
+ @Status TINYINT,
+ @OrganizationId UNIQUEIDENTIFIER
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ SELECT
+ *
+ FROM
+ [dbo].[OrganizationUserOrganizationDetailsView]
+ WHERE
+ [UserId] = @UserId
+ AND [OrganizationId] = @OrganizationId
+ AND (@Status IS NULL OR [Status] = @Status)
+END
\ No newline at end of file
diff --git a/util/Migrator/DbScripts/2020_12_04_00_OrgUserReadByOrgEmail.sql b/util/Migrator/DbScripts/2020-12-04_00_OrgUserReadByOrgEmail.sql
similarity index 100%
rename from util/Migrator/DbScripts/2020_12_04_00_OrgUserReadByOrgEmail.sql
rename to util/Migrator/DbScripts/2020-12-04_00_OrgUserReadByOrgEmail.sql
diff --git a/util/Migrator/DbScripts/2020-12-06_00_OrgUserOrgDetailsReadByUserIdStatusOrgId.sql b/util/Migrator/DbScripts/2020-12-06_00_OrgUserOrgDetailsReadByUserIdStatusOrgId.sql
new file mode 100644
index 0000000000..b17334e810
--- /dev/null
+++ b/util/Migrator/DbScripts/2020-12-06_00_OrgUserOrgDetailsReadByUserIdStatusOrgId.sql
@@ -0,0 +1,24 @@
+IF OBJECT_ID('[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]') IS NOT NULL
+BEGIN
+ DROP PROCEDURE [dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]
+END
+GO
+
+CREATE PROCEDURE [dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]
+ @UserId UNIQUEIDENTIFIER,
+ @Status TINYINT,
+ @OrganizationId UNIQUEIDENTIFIER
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ SELECT
+ *
+ FROM
+ [dbo].[OrganizationUserOrganizationDetailsView]
+ WHERE
+ [UserId] = @UserId
+ AND [OrganizationId] = @OrganizationId
+ AND (@Status IS NULL OR [Status] = @Status)
+END
+GO