using System.Data; using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; namespace Bit.Infrastructure.Dapper.Repositories; public class CollectionRepository : Repository, ICollectionRepository { public CollectionRepository(GlobalSettings globalSettings) : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) { } public CollectionRepository(string connectionString, string readOnlyConnectionString) : base(connectionString, readOnlyConnectionString) { } public async Task GetCountByOrganizationIdAsync(Guid organizationId) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteScalarAsync( "[dbo].[Collection_ReadCountByOrganizationId]", new { OrganizationId = organizationId }, commandType: CommandType.StoredProcedure); return results; } } public async Task> GetByIdWithAccessAsync(Guid id) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryMultipleAsync( $"[{Schema}].[Collection_ReadWithGroupsAndUsersById]", new { Id = id }, commandType: CommandType.StoredProcedure); var collection = await results.ReadFirstOrDefaultAsync(); var groups = (await results.ReadAsync()).ToList(); var users = (await results.ReadAsync()).ToList(); var access = new CollectionAccessDetails { Groups = groups, Users = users }; return new Tuple(collection, access); } } public async Task> GetManyByManyIdsAsync(IEnumerable collectionIds) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryAsync( $"[{Schema}].[Collection_ReadByIds]", new { Ids = collectionIds.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure); return results.ToList(); } } public async Task> GetManyByOrganizationIdAsync(Guid organizationId) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryAsync( $"[{Schema}].[{Table}_ReadByOrganizationId]", new { OrganizationId = organizationId }, commandType: CommandType.StoredProcedure); return results.ToList(); } } public async Task>> GetManyByOrganizationIdWithAccessAsync(Guid organizationId) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryMultipleAsync( $"[{Schema}].[Collection_ReadWithGroupsAndUsersByOrganizationId]", new { OrganizationId = organizationId }, commandType: CommandType.StoredProcedure); var collections = (await results.ReadAsync()); var groups = (await results.ReadAsync()) .GroupBy(g => g.CollectionId); var users = (await results.ReadAsync()) .GroupBy(u => u.CollectionId); return collections.Select(collection => new Tuple( collection, new CollectionAccessDetails { Groups = groups .FirstOrDefault(g => g.Key == collection.Id)? .Select(g => new CollectionAccessSelection { Id = g.GroupId, HidePasswords = g.HidePasswords, ReadOnly = g.ReadOnly, Manage = g.Manage }).ToList() ?? new List(), Users = users .FirstOrDefault(u => u.Key == collection.Id)? .Select(c => new CollectionAccessSelection { Id = c.OrganizationUserId, HidePasswords = c.HidePasswords, ReadOnly = c.ReadOnly, Manage = c.Manage }).ToList() ?? new List() } ) ).ToList(); } } public async Task> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections) { var sprocName = useFlexibleCollections ? $"[{Schema}].[Collection_ReadByUserId_V2]" : $"[{Schema}].[Collection_ReadByUserId]"; using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryAsync( sprocName, new { UserId = userId }, commandType: CommandType.StoredProcedure); return results.ToList(); } } public async Task> GetManyByOrganizationIdWithPermissionsAsync(Guid organizationId, Guid userId, bool includeAccessRelationships) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryMultipleAsync( $"[{Schema}].[Collection_ReadByOrganizationIdWithPermissions]", new { OrganizationId = organizationId, UserId = userId, IncludeAccessRelationships = includeAccessRelationships }, commandType: CommandType.StoredProcedure); var collections = (await results.ReadAsync()).ToList(); if (!includeAccessRelationships) { return collections; } var groups = (await results.ReadAsync()) .GroupBy(g => g.CollectionId) .ToList(); var users = (await results.ReadAsync()) .GroupBy(u => u.CollectionId) .ToList(); foreach (var collection in collections) { collection.Groups = groups .FirstOrDefault(g => g.Key == collection.Id)? .Select(g => new CollectionAccessSelection { Id = g.GroupId, HidePasswords = g.HidePasswords, ReadOnly = g.ReadOnly, Manage = g.Manage }).ToList() ?? new List(); collection.Users = users .FirstOrDefault(u => u.Key == collection.Id)? .Select(c => new CollectionAccessSelection { Id = c.OrganizationUserId, HidePasswords = c.HidePasswords, ReadOnly = c.ReadOnly, Manage = c.Manage }).ToList() ?? new List(); } return collections; } } public async Task GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, bool includeAccessRelationships) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryMultipleAsync( $"[{Schema}].[Collection_ReadByIdWithPermissions]", new { CollectionId = collectionId, UserId = userId, IncludeAccessRelationships = includeAccessRelationships }, commandType: CommandType.StoredProcedure); var collectionDetails = await results.ReadFirstOrDefaultAsync(); if (!includeAccessRelationships || collectionDetails == null) return collectionDetails; collectionDetails.Groups = (await results.ReadAsync()).ToList(); collectionDetails.Users = (await results.ReadAsync()).ToList(); return collectionDetails; } } public async Task CreateAsync(Collection obj, IEnumerable groups, IEnumerable users) { obj.SetNewId(); var objWithGroupsAndUsers = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); objWithGroupsAndUsers.Users = users != null ? users.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( $"[{Schema}].[Collection_CreateWithGroupsAndUsers]", objWithGroupsAndUsers, commandType: CommandType.StoredProcedure); } } public async Task ReplaceAsync(Collection obj, IEnumerable groups, IEnumerable users) { var objWithGroupsAndUsers = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); objWithGroupsAndUsers.Users = users != null ? users.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( $"[{Schema}].[Collection_UpdateWithGroupsAndUsers]", objWithGroupsAndUsers, commandType: CommandType.StoredProcedure); } } public async Task DeleteManyAsync(IEnumerable collectionIds) { using (var connection = new SqlConnection(ConnectionString)) { await connection.ExecuteAsync("[dbo].[Collection_DeleteByIds]", new { Ids = collectionIds.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure); } } public async Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable collectionIds, IEnumerable users, IEnumerable groups) { using (var connection = new SqlConnection(ConnectionString)) { var usersArray = users != null ? users.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); var groupsArray = groups != null ? groups.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); var results = await connection.ExecuteAsync( $"[{Schema}].[Collection_CreateOrUpdateAccessForMany]", new { OrganizationId = organizationId, CollectionIds = collectionIds.ToGuidIdArrayTVP(), Users = usersArray, Groups = groupsArray }, commandType: CommandType.StoredProcedure); } } public async Task CreateUserAsync(Guid collectionId, Guid organizationUserId) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( $"[{Schema}].[CollectionUser_Create]", new { CollectionId = collectionId, OrganizationUserId = organizationUserId }, commandType: CommandType.StoredProcedure); } } public async Task DeleteUserAsync(Guid collectionId, Guid organizationUserId) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( $"[{Schema}].[CollectionUser_Delete]", new { CollectionId = collectionId, OrganizationUserId = organizationUserId }, commandType: CommandType.StoredProcedure); } } public async Task UpdateUsersAsync(Guid id, IEnumerable users) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( $"[{Schema}].[CollectionUser_UpdateUsers]", new { CollectionId = id, Users = users.ToArrayTVP() }, commandType: CommandType.StoredProcedure); } } public async Task> GetManyUsersByIdAsync(Guid id) { using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryAsync( $"[{Schema}].[CollectionUser_ReadByCollectionId]", new { CollectionId = id }, commandType: CommandType.StoredProcedure); return results.ToList(); } } public class CollectionWithGroupsAndUsers : Collection { public DataTable Groups { get; set; } public DataTable Users { get; set; } } }