1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

collection user refactor

This commit is contained in:
Kyle Spearrin
2017-05-11 14:52:35 -04:00
parent d7f9977382
commit 21d1cd6adc
43 changed files with 318 additions and 504 deletions

View File

@ -1,35 +0,0 @@
using System;
using Bit.Core.Models.Table;
using System.Collections.Generic;
using System.Linq;
namespace Bit.Core.Models.Api
{
public class CollectionUserCollectionRequestModel
{
public string UserId { get; set; }
public IEnumerable<Collection> Collections { get; set; }
public IEnumerable<CollectionUser> ToCollectionUsers()
{
return Collections.Select(c => new CollectionUser
{
OrganizationUserId = new Guid(UserId),
CollectionId = new Guid(c.CollectionId),
ReadOnly = c.ReadOnly
});
}
public class Collection
{
public string CollectionId { get; set; }
public bool ReadOnly { get; set; }
}
}
public class CollectionUserUserRequestModel
{
public string UserId { get; set; }
public bool ReadOnly { get; set; }
}
}

View File

@ -12,15 +12,6 @@ namespace Bit.Core.Models.Api
public string Id { get; set; }
public bool ReadOnly { get; set; }
public CollectionUser ToCollectionUser()
{
return new CollectionUser
{
ReadOnly = ReadOnly,
CollectionId = new Guid(Id)
};
}
public SelectionReadOnly ToSelectionReadOnly()
{
return new SelectionReadOnly

View File

@ -1,5 +1,4 @@
using System;
using Bit.Core.Models.Table;
using Bit.Core.Models.Data;
using Bit.Core.Enums;
@ -7,7 +6,7 @@ namespace Bit.Core.Models.Api
{
public class CollectionUserResponseModel : ResponseModel
{
public CollectionUserResponseModel(CollectionUserUserDetails collectionUser)
public CollectionUserResponseModel(CollectionUserDetails collectionUser)
: base("collectionUser")
{
if(collectionUser == null)
@ -15,9 +14,7 @@ namespace Bit.Core.Models.Api
throw new ArgumentNullException(nameof(collectionUser));
}
Id = collectionUser.Id?.ToString();
OrganizationUserId = collectionUser.OrganizationUserId.ToString();
CollectionId = collectionUser.CollectionId?.ToString();
AccessAll = collectionUser.AccessAll;
Name = collectionUser.Name;
Email = collectionUser.Email;
@ -26,9 +23,7 @@ namespace Bit.Core.Models.Api
ReadOnly = collectionUser.ReadOnly;
}
public string Id { get; set; }
public string OrganizationUserId { get; set; }
public string CollectionId { get; set; }
public bool AccessAll { get; set; }
public string Name { get; set; }
public string Email { get; set; }

View File

@ -6,7 +6,7 @@ namespace Bit.Core.Models.Api
{
public class GroupUserResponseModel : ResponseModel
{
public GroupUserResponseModel(GroupUserUserDetails groupUser)
public GroupUserResponseModel(GroupUserDetails groupUser)
: base("groupUser")
{
if(groupUser == null)
@ -15,7 +15,6 @@ namespace Bit.Core.Models.Api
}
OrganizationUserId = groupUser.OrganizationUserId.ToString();
GroupId = groupUser.GroupId.ToString();
AccessAll = groupUser.AccessAll;
Name = groupUser.Name;
Email = groupUser.Email;
@ -24,7 +23,6 @@ namespace Bit.Core.Models.Api
}
public string OrganizationUserId { get; set; }
public string GroupId { get; set; }
public bool AccessAll { get; set; }
public string Name { get; set; }
public string Email { get; set; }

View File

@ -2,15 +2,14 @@
namespace Bit.Core.Models.Data
{
public class GroupUserUserDetails
public class CollectionUserDetails
{
public Guid OrganizationUserId { get; set; }
public Guid OrganizationId { get; set; }
public Guid GroupId { get; set; }
public bool AccessAll { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Enums.OrganizationUserStatusType Status { get; set; }
public Enums.OrganizationUserType Type { get; set; }
public bool ReadOnly { get; set; }
}
}

View File

@ -2,17 +2,13 @@
namespace Bit.Core.Models.Data
{
public class CollectionUserUserDetails
public class GroupUserDetails
{
public Guid? Id { get; set; }
public Guid OrganizationUserId { get; set; }
public Guid? OrganizationId { get; set; }
public Guid? CollectionId { get; set; }
public bool AccessAll { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Enums.OrganizationUserStatusType Status { get; set; }
public Enums.OrganizationUserType Type { get; set; }
public bool ReadOnly { get; set; }
}
}

View File

@ -1,11 +0,0 @@
using System;
namespace Bit.Core.Models.Table
{
public class CollectionGroup
{
public Guid CollectionId { get; set; }
public Guid GroupId { get; set; }
public bool ReadOnly { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using System;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Table
{
public class CollectionUser : IDataObject<Guid>
{
public Guid Id { get; set; }
public Guid CollectionId { get; set; }
public Guid OrganizationUserId { get; set; }
public bool ReadOnly { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
public void SetNewId()
{
Id = CoreHelpers.GenerateComb();
}
}
}

View File

@ -12,8 +12,9 @@ namespace Bit.Core.Repositories
Task<Tuple<Collection, ICollection<SelectionReadOnly>>> GetByIdWithGroupsAsync(Guid id);
Task<ICollection<Collection>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<ICollection<Collection>> GetManyByUserIdAsync(Guid userId);
Task<ICollection<CollectionUserDetails>> GetManyUserDetailsByIdAsync(Guid organizationId, Guid collectionId);
Task CreateAsync(Collection obj, IEnumerable<SelectionReadOnly> groups);
Task ReplaceAsync(Collection obj, IEnumerable<SelectionReadOnly> groups);
Task DeleteUserAsync(Guid collectionId, Guid organizationUserId);
}
}

View File

@ -1,14 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Table;
using System.Collections.Generic;
using Bit.Core.Models.Data;
namespace Bit.Core.Repositories
{
public interface ICollectionUserRepository : IRepository<CollectionUser, Guid>
{
Task<ICollection<CollectionUser>> GetManyByOrganizationUserIdAsync(Guid orgUserId);
Task<ICollection<CollectionUserUserDetails>> GetManyDetailsByCollectionIdAsync(Guid organizationId, Guid collectionId);
}
}

View File

@ -10,7 +10,7 @@ namespace Bit.Core.Repositories
{
Task<Tuple<Group, ICollection<SelectionReadOnly>>> GetByIdWithCollectionsAsync(Guid id);
Task<ICollection<Group>> GetManyByOrganizationIdAsync(Guid organizationId);
Task<ICollection<GroupUserUserDetails>> GetManyUserDetailsByIdAsync(Guid id);
Task<ICollection<GroupUserDetails>> GetManyUserDetailsByIdAsync(Guid id);
Task<ICollection<Guid>> GetManyIdsByUserIdAsync(Guid organizationUserId);
Task CreateAsync(Group obj, IEnumerable<SelectionReadOnly> collections);
Task ReplaceAsync(Group obj, IEnumerable<SelectionReadOnly> collections);

View File

@ -21,5 +21,7 @@ namespace Bit.Core.Repositories
Task<ICollection<OrganizationUserOrganizationDetails>> GetManyDetailsByUserAsync(Guid userId,
OrganizationUserStatusType? status = null);
Task UpdateGroupsAsync(Guid orgUserId, IEnumerable<Guid> groupIds);
Task CreateAsync(OrganizationUser obj, IEnumerable<SelectionReadOnly> collections);
Task ReplaceAsync(OrganizationUser obj, IEnumerable<SelectionReadOnly> collections);
}
}

View File

@ -81,6 +81,24 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<ICollection<CollectionUserDetails>> GetManyUserDetailsByIdAsync(Guid organizationId,
Guid collectionId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CollectionUserDetails>(
$"[{Schema}].[CollectionUserDetails_ReadByCollectionId]",
new { OrganizationId = organizationId, CollectionId = collectionId },
commandType: CommandType.StoredProcedure);
// Return distinct Id results. If at least one of the grouped results is not ReadOnly, that we return it.
return results
.GroupBy(c => c.OrganizationUserId)
.Select(g => g.OrderBy(og => og.ReadOnly).First())
.ToList();
}
}
public async Task CreateAsync(Collection obj, IEnumerable<SelectionReadOnly> groups)
{
obj.SetNewId();
@ -110,6 +128,17 @@ namespace Bit.Core.Repositories.SqlServer
}
}
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 class CollectionWithGroups : Collection
{
public DataTable Groups { get; set; }

View File

@ -1,54 +0,0 @@
using System;
using Bit.Core.Models.Table;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using Dapper;
using System.Linq;
using Bit.Core.Models.Data;
namespace Bit.Core.Repositories.SqlServer
{
public class CollectionUserRepository : Repository<CollectionUser, Guid>, ICollectionUserRepository
{
public CollectionUserRepository(GlobalSettings globalSettings)
: this(globalSettings.SqlServer.ConnectionString)
{ }
public CollectionUserRepository(string connectionString)
: base(connectionString)
{ }
public async Task<ICollection<CollectionUser>> GetManyByOrganizationUserIdAsync(Guid orgUserId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CollectionUser>(
$"[{Schema}].[{Table}_ReadByOrganizationUserId]",
new { OrganizationUserId = orgUserId },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
public async Task<ICollection<CollectionUserUserDetails>> GetManyDetailsByCollectionIdAsync(Guid organizationId,
Guid collectionId)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<CollectionUserUserDetails>(
$"[{Schema}].[CollectionUserUserDetails_ReadByCollectionId]",
new { OrganizationId = organizationId, CollectionId = collectionId },
commandType: CommandType.StoredProcedure);
// Return distinct Id results. If at least one of the grouped results is not ReadOnly, that we return it.
return results
.GroupBy(c => c.Id)
.Select(g => g.OrderBy(og => og.ReadOnly).First())
.ToList();
}
}
}
}

View File

@ -51,12 +51,12 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<ICollection<GroupUserUserDetails>> GetManyUserDetailsByIdAsync(Guid id)
public async Task<ICollection<GroupUserDetails>> GetManyUserDetailsByIdAsync(Guid id)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<GroupUserUserDetails>(
$"[{Schema}].[GroupUserUserDetails_ReadByGroupId]",
var results = await connection.QueryAsync<GroupUserDetails>(
$"[{Schema}].[GroupUserDetails_ReadByGroupId]",
new { GroupId = id },
commandType: CommandType.StoredProcedure);

View File

@ -9,6 +9,7 @@ using Bit.Core.Models.Data;
using System.Collections.Generic;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Core.Repositories.SqlServer
{
@ -166,5 +167,41 @@ namespace Bit.Core.Repositories.SqlServer
commandType: CommandType.StoredProcedure);
}
}
public async Task CreateAsync(OrganizationUser obj, IEnumerable<SelectionReadOnly> collections)
{
obj.SetNewId();
var objWithCollections = JsonConvert.DeserializeObject<OrganizationUserWithCollections>(
JsonConvert.SerializeObject(obj));
objWithCollections.Collections = collections.ToArrayTVP();
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[OrganizationUser_CreateWithCollections]",
objWithCollections,
commandType: CommandType.StoredProcedure);
}
}
public async Task ReplaceAsync(OrganizationUser obj, IEnumerable<SelectionReadOnly> collections)
{
var objWithCollections = JsonConvert.DeserializeObject<OrganizationUserWithCollections>(
JsonConvert.SerializeObject(obj));
objWithCollections.Collections = collections.ToArrayTVP();
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.ExecuteAsync(
$"[{Schema}].[OrganizationUser_UpdateWithCollections]",
objWithCollections,
commandType: CommandType.StoredProcedure);
}
}
public class OrganizationUserWithCollections : OrganizationUser
{
public DataTable Collections { get; set; }
}
}
}

View File

@ -4,6 +4,7 @@ using Bit.Core.Models.Table;
using System;
using System.Collections.Generic;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Services
{
@ -21,11 +22,11 @@ namespace Bit.Core.Services
Task EnableAsync(Guid organizationId);
Task UpdateAsync(Organization organization, bool updateBilling = false);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email,
OrganizationUserType type, bool accessAll, IEnumerable<CollectionUser> collections);
OrganizationUserType type, bool accessAll, IEnumerable<SelectionReadOnly> collections);
Task ResendInviteAsync(Guid organizationId, Guid invitingUserId, Guid organizationUserId);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId);
Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<CollectionUser> collections);
Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<SelectionReadOnly> collections);
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId);
Task DeleteUserAsync(Guid organizationId, Guid userId);
}

View File

@ -13,7 +13,6 @@ namespace Bit.Core.Services
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionUserRepository _collectionUserRepository;
private readonly IUserRepository _userRepository;
private readonly IMailService _mailService;
@ -21,14 +20,12 @@ namespace Bit.Core.Services
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
ICollectionUserRepository collectionUserRepository,
IUserRepository userRepository,
IMailService mailService)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_collectionRepository = collectionRepository;
_collectionUserRepository = collectionUserRepository;
_userRepository = userRepository;
_mailService = mailService;
}

View File

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.DataProtection;
using Stripe;
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
using Bit.Core.Models.Data;
namespace Bit.Core.Services
{
@ -19,7 +20,6 @@ namespace Bit.Core.Services
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ICollectionRepository _collectionRepository;
private readonly ICollectionUserRepository _collectionUserRepository;
private readonly IUserRepository _userRepository;
private readonly IDataProtector _dataProtector;
private readonly IMailService _mailService;
@ -29,7 +29,6 @@ namespace Bit.Core.Services
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
ICollectionUserRepository collectionUserRepository,
IUserRepository userRepository,
IDataProtectionProvider dataProtectionProvider,
IMailService mailService,
@ -38,7 +37,6 @@ namespace Bit.Core.Services
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_collectionRepository = collectionRepository;
_collectionUserRepository = collectionUserRepository;
_userRepository = userRepository;
_dataProtector = dataProtectionProvider.CreateProtector("OrganizationServiceDataProtector");
_mailService = mailService;
@ -683,7 +681,7 @@ namespace Bit.Core.Services
}
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email,
OrganizationUserType type, bool accessAll, IEnumerable<CollectionUser> collections)
OrganizationUserType type, bool accessAll, IEnumerable<SelectionReadOnly> collections)
{
var organization = await _organizationRepository.GetByIdAsync(organizationId);
if(organization == null)
@ -721,13 +719,16 @@ namespace Bit.Core.Services
RevisionDate = DateTime.UtcNow
};
await _organizationUserRepository.CreateAsync(orgUser);
if(!orgUser.AccessAll && collections.Any())
{
await SaveUserCollectionsAsync(orgUser, collections, true);
await _organizationUserRepository.CreateAsync(orgUser, collections);
}
else
{
await _organizationUserRepository.CreateAsync(orgUser);
}
await SendInviteAsync(orgUser);
await SendInviteAsync(orgUser);
return orgUser;
}
@ -833,7 +834,7 @@ namespace Bit.Core.Services
return orgUser;
}
public async Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<CollectionUser> collections)
public async Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<SelectionReadOnly> collections)
{
if(user.Id.Equals(default(Guid)))
{
@ -846,14 +847,13 @@ namespace Bit.Core.Services
throw new BadRequestException("Organization must have at least one confirmed owner.");
}
await _organizationUserRepository.ReplaceAsync(user);
if(user.AccessAll)
{
// We don't need any collections if we're flagged to have all access.
collections = new List<CollectionUser>();
collections = new List<SelectionReadOnly>();
}
await SaveUserCollectionsAsync(user, collections, false);
await _organizationUserRepository.ReplaceAsync(user, collections);
}
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId)
@ -901,41 +901,5 @@ namespace Bit.Core.Services
Enums.OrganizationUserType.Owner);
return owners.Where(o => o.Status == Enums.OrganizationUserStatusType.Confirmed);
}
private async Task SaveUserCollectionsAsync(OrganizationUser user, IEnumerable<CollectionUser> collections, bool newUser)
{
if(collections == null)
{
collections = new List<CollectionUser>();
}
var orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(user.OrganizationId);
var currentUserCollections = newUser ? null : await _collectionUserRepository.GetManyByOrganizationUserIdAsync(user.Id);
// Let's make sure all these belong to this user and organization.
var filteredCollections = collections.Where(c => orgCollections.Any(os => os.Id == c.CollectionId));
foreach(var collection in filteredCollections)
{
var existingCollectionUser = currentUserCollections?.FirstOrDefault(cu => cu.CollectionId == collection.CollectionId);
if(existingCollectionUser != null)
{
collection.Id = existingCollectionUser.Id;
collection.CreationDate = existingCollectionUser.CreationDate;
}
collection.OrganizationUserId = user.Id;
await _collectionUserRepository.UpsertAsync(collection);
}
if(!newUser)
{
var collectionsToDelete = currentUserCollections.Where(cu =>
!filteredCollections.Any(c => c.CollectionId == cu.CollectionId));
foreach(var collection in collectionsToDelete)
{
await _collectionUserRepository.DeleteAsync(collection);
}
}
}
}
}

View File

@ -32,7 +32,6 @@ namespace Bit.Core.Utilities
services.AddSingleton<IOrganizationRepository, SqlServerRepos.OrganizationRepository>();
services.AddSingleton<IOrganizationUserRepository, SqlServerRepos.OrganizationUserRepository>();
services.AddSingleton<ICollectionRepository, SqlServerRepos.CollectionRepository>();
services.AddSingleton<ICollectionUserRepository, SqlServerRepos.CollectionUserRepository>();
services.AddSingleton<IFolderRepository, SqlServerRepos.FolderRepository>();
services.AddSingleton<ICollectionCipherRepository, SqlServerRepos.CollectionCipherRepository>();
services.AddSingleton<IGroupRepository, SqlServerRepos.GroupRepository>();