1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-21 11:34:31 -05:00

[PM-8004] Move Unmanaged collection logic out of component for better reuse (#4108)

* Updated sprocs to return unmanaged collection column, updated reponse to return to return unmanaged

* reformatted sproc
This commit is contained in:
SmithThe4th 2024-05-21 14:42:47 -04:00 committed by GitHub
parent b863454a4e
commit aee180adfc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 256 additions and 9 deletions

View File

@ -89,6 +89,7 @@ public class CollectionAccessDetailsResponseModel : CollectionResponseModel
ReadOnly = collection.ReadOnly; ReadOnly = collection.ReadOnly;
HidePasswords = collection.HidePasswords; HidePasswords = collection.HidePasswords;
Manage = collection.Manage; Manage = collection.Manage;
Unmanaged = collection.Unmanaged;
Groups = collection.Groups?.Select(g => new SelectionReadOnlyResponseModel(g)) ?? Enumerable.Empty<SelectionReadOnlyResponseModel>(); Groups = collection.Groups?.Select(g => new SelectionReadOnlyResponseModel(g)) ?? Enumerable.Empty<SelectionReadOnlyResponseModel>();
Users = collection.Users?.Select(g => new SelectionReadOnlyResponseModel(g)) ?? Enumerable.Empty<SelectionReadOnlyResponseModel>(); Users = collection.Users?.Select(g => new SelectionReadOnlyResponseModel(g)) ?? Enumerable.Empty<SelectionReadOnlyResponseModel>();
} }
@ -104,4 +105,5 @@ public class CollectionAccessDetailsResponseModel : CollectionResponseModel
public bool ReadOnly { get; set; } public bool ReadOnly { get; set; }
public bool HidePasswords { get; set; } public bool HidePasswords { get; set; }
public bool Manage { get; set; } public bool Manage { get; set; }
public bool Unmanaged { get; set; }
} }

View File

@ -14,4 +14,9 @@ public class CollectionAdminDetails : CollectionDetails
/// Flag for whether the user has been explicitly assigned to the collection either directly or through a group. /// Flag for whether the user has been explicitly assigned to the collection either directly or through a group.
/// </summary> /// </summary>
public bool Assigned { get; set; } public bool Assigned { get; set; }
/// <summary>
/// Flag for whether a collection is managed by an active user or group.
/// </summary>
public bool Unmanaged { get; set; }
} }

View File

@ -391,7 +391,8 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
c.Name, c.Name,
c.CreationDate, c.CreationDate,
c.RevisionDate, c.RevisionDate,
c.ExternalId c.ExternalId,
c.Unmanaged
}).Select(collectionGroup => new CollectionAdminDetails }).Select(collectionGroup => new CollectionAdminDetails
{ {
Id = collectionGroup.Key.Id, Id = collectionGroup.Key.Id,
@ -404,7 +405,8 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
HidePasswords = HidePasswords =
Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))), Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))),
Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))), Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))),
Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))) Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))),
Unmanaged = collectionGroup.Key.Unmanaged
}).ToList(); }).ToList();
} }
else else
@ -417,7 +419,8 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
c.Name, c.Name,
c.CreationDate, c.CreationDate,
c.RevisionDate, c.RevisionDate,
c.ExternalId c.ExternalId,
c.Unmanaged
} }
into collectionGroup into collectionGroup
select new CollectionAdminDetails select new CollectionAdminDetails
@ -432,7 +435,8 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
HidePasswords = HidePasswords =
Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))), Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))),
Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))), Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))),
Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))) Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))),
Unmanaged = collectionGroup.Key.Unmanaged
}).ToListAsync(); }).ToListAsync();
} }
@ -511,7 +515,8 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
HidePasswords = HidePasswords =
Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))), Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))),
Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))), Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))),
Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))) Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))),
Unmanaged = collectionGroup.Select(c => c.Unmanaged).FirstOrDefault()
}).FirstOrDefault(); }).FirstOrDefault();
} }
else else
@ -539,7 +544,8 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
HidePasswords = HidePasswords =
Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))), Convert.ToBoolean(collectionGroup.Min(c => Convert.ToInt32(c.HidePasswords))),
Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))), Manage = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Manage))),
Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))) Assigned = Convert.ToBoolean(collectionGroup.Max(c => Convert.ToInt32(c.Assigned))),
Unmanaged = collectionGroup.Select(c => c.Unmanaged).FirstOrDefault()
}).FirstOrDefaultAsync(); }).FirstOrDefaultAsync();
} }

View File

@ -1,4 +1,5 @@
using Bit.Core.Models.Data; using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
@ -46,6 +47,17 @@ public class CollectionAdminDetailsQuery : IQuery<CollectionAdminDetails>
from cg in cg_g.DefaultIfEmpty() from cg in cg_g.DefaultIfEmpty()
select new { c, cu, cg }; select new { c, cu, cg };
// Subqueries to determine if a colection is managed by an active user or group.
var activeUserManageRights = from cu in dbContext.CollectionUsers
join ou in dbContext.OrganizationUsers
on cu.OrganizationUserId equals ou.Id
where ou.Status == OrganizationUserStatusType.Confirmed && cu.Manage
select cu.CollectionId;
var activeGroupManageRights = from cg in dbContext.CollectionGroups
where cg.Manage
select cg.CollectionId;
if (_organizationId.HasValue) if (_organizationId.HasValue)
{ {
baseCollectionQuery = baseCollectionQuery.Where(x => x.c.OrganizationId == _organizationId); baseCollectionQuery = baseCollectionQuery.Where(x => x.c.OrganizationId == _organizationId);
@ -71,6 +83,7 @@ public class CollectionAdminDetailsQuery : IQuery<CollectionAdminDetails>
HidePasswords = (bool?)x.cu.HidePasswords ?? (bool?)x.cg.HidePasswords ?? false, HidePasswords = (bool?)x.cu.HidePasswords ?? (bool?)x.cg.HidePasswords ?? false,
Manage = (bool?)x.cu.Manage ?? (bool?)x.cg.Manage ?? false, Manage = (bool?)x.cu.Manage ?? (bool?)x.cg.Manage ?? false,
Assigned = x.cu != null || x.cg != null, Assigned = x.cu != null || x.cg != null,
Unmanaged = !activeUserManageRights.Contains(x.c.Id) && !activeGroupManageRights.Contains(x.c.Id),
}); });
} }

View File

@ -31,7 +31,29 @@ BEGIN
CU.[CollectionId] IS NULL AND CG.[CollectionId] IS NULL CU.[CollectionId] IS NULL AND CG.[CollectionId] IS NULL
THEN 0 THEN 0
ELSE 1 ELSE 1
END) AS [Assigned] END) AS [Assigned],
CASE
WHEN
-- No active user or group has manage rights
NOT EXISTS(
SELECT 1
FROM [dbo].[CollectionUser] CU2
JOIN [dbo].[OrganizationUser] OU2 ON CU2.[OrganizationUserId] = OU2.[Id]
WHERE
CU2.[CollectionId] = C.[Id] AND
OU2.[Status] = 2 AND
CU2.[Manage] = 1
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroup] CG2
WHERE
CG2.[CollectionId] = C.[Id] AND
CG2.[Manage] = 1
)
THEN 1
ELSE 0
END AS [Unmanaged]
FROM FROM
[dbo].[CollectionView] C [dbo].[CollectionView] C
LEFT JOIN LEFT JOIN

View File

@ -31,7 +31,29 @@ BEGIN
CU.[CollectionId] IS NULL AND CG.[CollectionId] IS NULL CU.[CollectionId] IS NULL AND CG.[CollectionId] IS NULL
THEN 0 THEN 0
ELSE 1 ELSE 1
END) AS [Assigned] END) AS [Assigned],
CASE
WHEN
-- No active user or group has manage rights
NOT EXISTS(
SELECT 1
FROM [dbo].[CollectionUser] CU2
JOIN [dbo].[OrganizationUser] OU2 ON CU2.[OrganizationUserId] = OU2.[Id]
WHERE
CU2.[CollectionId] = C.[Id] AND
OU2.[Status] = 2 AND
CU2.[Manage] = 1
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroup] CG2
WHERE
CG2.[CollectionId] = C.[Id] AND
CG2.[Manage] = 1
)
THEN 1
ELSE 0
END AS [Unmanaged]
FROM FROM
[dbo].[CollectionView] C [dbo].[CollectionView] C
LEFT JOIN LEFT JOIN

View File

@ -310,6 +310,7 @@ public class CollectionRepositoryTests
Assert.True(c1.Manage); Assert.True(c1.Manage);
Assert.False(c1.ReadOnly); Assert.False(c1.ReadOnly);
Assert.False(c1.HidePasswords); Assert.False(c1.HidePasswords);
Assert.False(c1.Unmanaged);
}, c2 => }, c2 =>
{ {
Assert.NotNull(c2); Assert.NotNull(c2);
@ -319,6 +320,7 @@ public class CollectionRepositoryTests
Assert.False(c2.Manage); Assert.False(c2.Manage);
Assert.True(c2.ReadOnly); Assert.True(c2.ReadOnly);
Assert.False(c2.HidePasswords); Assert.False(c2.HidePasswords);
Assert.True(c2.Unmanaged);
}, c3 => }, c3 =>
{ {
Assert.NotNull(c3); Assert.NotNull(c3);
@ -328,6 +330,7 @@ public class CollectionRepositoryTests
Assert.False(c3.Manage); Assert.False(c3.Manage);
Assert.False(c3.ReadOnly); Assert.False(c3.ReadOnly);
Assert.False(c3.HidePasswords); Assert.False(c3.HidePasswords);
Assert.False(c3.Unmanaged);
}); });
} }
@ -436,6 +439,7 @@ public class CollectionRepositoryTests
Assert.True(c1.Manage); Assert.True(c1.Manage);
Assert.False(c1.ReadOnly); Assert.False(c1.ReadOnly);
Assert.False(c1.HidePasswords); Assert.False(c1.HidePasswords);
Assert.False(c1.Unmanaged);
}, c2 => }, c2 =>
{ {
Assert.NotNull(c2); Assert.NotNull(c2);
@ -445,6 +449,7 @@ public class CollectionRepositoryTests
Assert.False(c2.Manage); Assert.False(c2.Manage);
Assert.True(c2.ReadOnly); Assert.True(c2.ReadOnly);
Assert.False(c2.HidePasswords); Assert.False(c2.HidePasswords);
Assert.True(c2.Unmanaged);
}, c3 => }, c3 =>
{ {
Assert.NotNull(c3); Assert.NotNull(c3);
@ -454,6 +459,7 @@ public class CollectionRepositoryTests
Assert.True(c3.Manage); // Group 2 is Manage Assert.True(c3.Manage); // Group 2 is Manage
Assert.False(c3.ReadOnly); Assert.False(c3.ReadOnly);
Assert.False(c3.HidePasswords); Assert.False(c3.HidePasswords);
Assert.False(c3.Unmanaged);
}); });
} }
} }

View File

@ -0,0 +1,171 @@
CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadByOrganizationIdWithPermissions]
@OrganizationId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@IncludeAccessRelationships BIT
AS
BEGIN
SET NOCOUNT ON
SELECT
C.*,
MIN(CASE
WHEN
COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0
THEN 0
ELSE 1
END) AS [ReadOnly],
MIN(CASE
WHEN
COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0
THEN 0
ELSE 1
END) AS [HidePasswords],
MAX(CASE
WHEN
COALESCE(CU.[Manage], CG.[Manage], 0) = 0
THEN 0
ELSE 1
END) AS [Manage],
MAX(CASE
WHEN
CU.[CollectionId] IS NULL AND CG.[CollectionId] IS NULL
THEN 0
ELSE 1
END) AS [Assigned],
CASE
WHEN
-- No active user or group has manage rights
NOT EXISTS(
SELECT 1
FROM [dbo].[CollectionUser] CU2
JOIN [dbo].[OrganizationUser] OU2 ON CU2.[OrganizationUserId] = OU2.[Id]
WHERE
CU2.[CollectionId] = C.[Id] AND
OU2.[Status] = 2 AND
CU2.[Manage] = 1
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroup] CG2
WHERE
CG2.[CollectionId] = C.[Id] AND
CG2.[Manage] = 1
)
THEN 1
ELSE 0
END AS [Unmanaged]
FROM
[dbo].[CollectionView] C
LEFT JOIN
[dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
C.[OrganizationId] = @OrganizationId
GROUP BY
C.[Id],
C.[OrganizationId],
C.[Name],
C.[CreationDate],
C.[RevisionDate],
C.[ExternalId]
IF (@IncludeAccessRelationships = 1)
BEGIN
EXEC [dbo].[CollectionGroup_ReadByOrganizationId] @OrganizationId
EXEC [dbo].[CollectionUser_ReadByOrganizationId] @OrganizationId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadByIdWithPermissions]
@CollectionId UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@IncludeAccessRelationships BIT
AS
BEGIN
SET NOCOUNT ON
SELECT
C.*,
MIN(CASE
WHEN
COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0
THEN 0
ELSE 1
END) AS [ReadOnly],
MIN (CASE
WHEN
COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0
THEN 0
ELSE 1
END) AS [HidePasswords],
MAX(CASE
WHEN
COALESCE(CU.[Manage], CG.[Manage], 0) = 0
THEN 0
ELSE 1
END) AS [Manage],
MAX(CASE
WHEN
CU.[CollectionId] IS NULL AND CG.[CollectionId] IS NULL
THEN 0
ELSE 1
END) AS [Assigned],
CASE
WHEN
-- No active user or group has manage rights
NOT EXISTS(
SELECT 1
FROM [dbo].[CollectionUser] CU2
JOIN [dbo].[OrganizationUser] OU2 ON CU2.[OrganizationUserId] = OU2.[Id]
WHERE
CU2.[CollectionId] = C.[Id] AND
OU2.[Status] = 2 AND
CU2.[Manage] = 1
)
AND NOT EXISTS (
SELECT 1
FROM [dbo].[CollectionGroup] CG2
WHERE
CG2.[CollectionId] = C.[Id] AND
CG2.[Manage] = 1
)
THEN 1
ELSE 0
END AS [Unmanaged]
FROM
[dbo].[CollectionView] C
LEFT JOIN
[dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId] AND OU.[UserId] = @UserId
LEFT JOIN
[dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id]
LEFT JOIN
[dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id]
LEFT JOIN
[dbo].[Group] G ON G.[Id] = GU.[GroupId]
LEFT JOIN
[dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId]
WHERE
C.[Id] = @CollectionId
GROUP BY
C.[Id],
C.[OrganizationId],
C.[Name],
C.[CreationDate],
C.[RevisionDate],
C.[ExternalId]
IF (@IncludeAccessRelationships = 1)
BEGIN
EXEC [dbo].[CollectionGroup_ReadByCollectionId] @CollectionId
EXEC [dbo].[CollectionUser_ReadByCollectionId] @CollectionId
END
END
GO