1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-07 05:58:13 -05:00

sync with user externalids

This commit is contained in:
Kyle Spearrin 2017-05-16 00:11:21 -04:00
parent b3e4fcca74
commit 933a3feade
9 changed files with 108 additions and 39 deletions

View File

@ -93,7 +93,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User); var userId = _userService.GetProperUserId(User);
var result = await _organizationService.InviteUserAsync(orgGuidId, userId.Value, model.Email, model.Type.Value, var result = await _organizationService.InviteUserAsync(orgGuidId, userId.Value, model.Email, model.Type.Value,
model.AccessAll, model.Collections?.Select(c => c.ToSelectionReadOnly())); model.AccessAll, null, model.Collections?.Select(c => c.ToSelectionReadOnly()));
} }
[HttpPut("{id}/reinvite")] [HttpPut("{id}/reinvite")]

View File

@ -242,9 +242,9 @@ namespace Bit.Api.Controllers
await _organizationService.ImportAsync( await _organizationService.ImportAsync(
orgIdGuid, orgIdGuid,
userId.Value, userId.Value,
model.Groups.Select(g => g.ToGroupTuple(orgIdGuid)), model.Groups.Select(g => g.ToImportedGroup(orgIdGuid)),
model.Users.Where(u => !u.Disabled).Select(u => u.Email), model.Users.Where(u => !u.Disabled).Select(u => u.ToImportedOrganizationUser()),
model.Users.Where(u => u.Disabled).Select(u => u.Email)); model.Users.Where(u => u.Disabled).Select(u => u.ExternalId));
} }
} }
} }

View File

@ -1,4 +1,5 @@
using Bit.Core.Models.Table; using Bit.Core.Models.Business;
using Bit.Core.Models.Table;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@ -18,25 +19,49 @@ namespace Bit.Core.Models.Api
public string ExternalId { get; set; } public string ExternalId { get; set; }
public IEnumerable<string> Users { get; set; } public IEnumerable<string> Users { get; set; }
public Tuple<Table.Group, HashSet<string>> ToGroupTuple(Guid organizationId) public ImportedGroup ToImportedGroup(Guid organizationId)
{ {
var group = new Table.Group var importedGroup = new ImportedGroup
{
Group = new Table.Group
{ {
OrganizationId = organizationId, OrganizationId = organizationId,
Name = Name, Name = Name,
ExternalId = ExternalId ExternalId = ExternalId
},
ExternalUserIds = new HashSet<string>(Users)
}; };
return new Tuple<Table.Group, HashSet<string>>(group, new HashSet<string>(Users)); return importedGroup;
} }
} }
public class User public class User : IValidatableObject
{ {
[Required]
[EmailAddress] [EmailAddress]
public string Email { get; set; } public string Email { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
[Required]
public string ExternalId { get; set; }
public ImportedOrganizationUser ToImportedOrganizationUser()
{
var importedUser = new ImportedOrganizationUser
{
Email = Email,
ExternalId = ExternalId
};
return importedUser;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(string.IsNullOrWhiteSpace(Email) && !Disabled)
{
yield return new ValidationResult("Email is required for enabled users.", new string[] { nameof(Email) });
}
}
} }
} }
} }

View File

@ -0,0 +1,11 @@
using Bit.Core.Models.Table;
using System.Collections.Generic;
namespace Bit.Core.Models.Business
{
public class ImportedGroup
{
public Group Group { get; set; }
public HashSet<string> ExternalUserIds { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace Bit.Core.Models.Business
{
public class ImportedOrganizationUser
{
public string Email { get; set; }
public string ExternalId { get; set; }
}
}

View File

@ -12,5 +12,6 @@ namespace Bit.Core.Models.Data
public Enums.OrganizationUserStatusType Status { get; set; } public Enums.OrganizationUserStatusType Status { get; set; }
public Enums.OrganizationUserType Type { get; set; } public Enums.OrganizationUserType Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public string ExternalId { get; set; }
} }
} }

View File

@ -22,14 +22,14 @@ namespace Bit.Core.Services
Task EnableAsync(Guid organizationId); Task EnableAsync(Guid organizationId);
Task UpdateAsync(Organization organization, bool updateBilling = false); Task UpdateAsync(Organization organization, bool updateBilling = false);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email, Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email,
OrganizationUserType type, bool accessAll, IEnumerable<SelectionReadOnly> collections); OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
Task ResendInviteAsync(Guid organizationId, Guid invitingUserId, Guid organizationUserId); Task ResendInviteAsync(Guid organizationId, Guid invitingUserId, Guid organizationUserId);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token); Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId); Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId);
Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<SelectionReadOnly> collections); Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<SelectionReadOnly> collections);
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId); Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId);
Task DeleteUserAsync(Guid organizationId, Guid userId); Task DeleteUserAsync(Guid organizationId, Guid userId);
Task ImportAsync(Guid organizationId, Guid importingUserId, IEnumerable<Tuple<Group, HashSet<string>>> groups, Task ImportAsync(Guid organizationId, Guid importingUserId, IEnumerable<ImportedGroup> groups,
IEnumerable<string> newUsers, IEnumerable<string> removeUsers); IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds);
} }
} }

View File

@ -684,7 +684,7 @@ namespace Bit.Core.Services
} }
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email, public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email,
OrganizationUserType type, bool accessAll, IEnumerable<SelectionReadOnly> collections) OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
{ {
var organization = await _organizationRepository.GetByIdAsync(organizationId); var organization = await _organizationRepository.GetByIdAsync(organizationId);
if(organization == null) if(organization == null)
@ -718,6 +718,7 @@ namespace Bit.Core.Services
Type = type, Type = type,
Status = OrganizationUserStatusType.Invited, Status = OrganizationUserStatusType.Invited,
AccessAll = accessAll, AccessAll = accessAll,
ExternalId = externalId,
CreationDate = DateTime.UtcNow, CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow RevisionDate = DateTime.UtcNow
}; };
@ -900,9 +901,9 @@ namespace Bit.Core.Services
public async Task ImportAsync(Guid organizationId, public async Task ImportAsync(Guid organizationId,
Guid importingUserId, Guid importingUserId,
IEnumerable<Tuple<Group, HashSet<string>>> groups, IEnumerable<ImportedGroup> groups,
IEnumerable<string> newUsers, IEnumerable<ImportedOrganizationUser> newUsers,
IEnumerable<string> removeUsers) IEnumerable<string> removeUserExternalIds)
{ {
var organization = await _organizationRepository.GetByIdAsync(organizationId); var organization = await _organizationRepository.GetByIdAsync(organizationId);
if(organization == null) if(organization == null)
@ -915,16 +916,17 @@ namespace Bit.Core.Services
throw new BadRequestException("Organization cannot use groups."); throw new BadRequestException("Organization cannot use groups.");
} }
var newUsersSet = new HashSet<string>(newUsers); var newUsersSet = new HashSet<string>(newUsers.Select(u => u.ExternalId));
var existingUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId); var existingUsersOriginal = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId);
var existingUsersIdDict = existingUsers.ToDictionary(u => u.Email, u => u.Id); var existingUsers = existingUsersOriginal.Where(u => !string.IsNullOrWhiteSpace(u.ExternalId)).ToList();
var existingUsersIdDict = existingUsers.ToDictionary(u => u.ExternalId, u => u.Id);
// Users // Users
// Remove Users // Remove Users
if(removeUsers.Any()) if(removeUserExternalIds.Any())
{ {
var removeUsersSet = new HashSet<string>(removeUsers); var removeUsersSet = new HashSet<string>(removeUserExternalIds);
var existingUsersDict = existingUsers.ToDictionary(u => u.Email); var existingUsersDict = existingUsers.ToDictionary(u => u.ExternalId);
var usersToRemove = removeUsersSet var usersToRemove = removeUsersSet
.Except(newUsersSet) .Except(newUsersSet)
@ -934,14 +936,14 @@ namespace Bit.Core.Services
foreach(var user in usersToRemove) foreach(var user in usersToRemove)
{ {
await _organizationUserRepository.DeleteAsync(new OrganizationUser { Id = user.Id }); await _organizationUserRepository.DeleteAsync(new OrganizationUser { Id = user.Id });
existingUsersIdDict.Remove(user.Email); existingUsersIdDict.Remove(user.ExternalId);
} }
} }
// Add new users // Add new users
if(newUsers.Any()) if(newUsers.Any())
{ {
var existingUsersSet = new HashSet<string>(existingUsers.Select(u => u.Email)); var existingUsersSet = new HashSet<string>(existingUsers.Select(u => u.ExternalId));
var usersToAdd = newUsersSet.Except(existingUsersSet).ToList(); var usersToAdd = newUsersSet.Except(existingUsersSet).ToList();
var seatsAvailable = int.MaxValue; var seatsAvailable = int.MaxValue;
@ -956,38 +958,60 @@ namespace Bit.Core.Services
} }
} }
foreach(var user in usersToAdd) foreach(var user in newUsers)
{ {
if(!usersToAdd.Contains(user.ExternalId) || string.IsNullOrWhiteSpace(user.Email))
{
continue;
}
try try
{ {
var newUser = await InviteUserAsync(organizationId, importingUserId, user, OrganizationUserType.User, var newUser = await InviteUserAsync(organizationId, importingUserId, user.Email,
false, new List<SelectionReadOnly>()); OrganizationUserType.User, false, user.ExternalId, new List<SelectionReadOnly>());
existingUsersIdDict.Add(newUser.Email, newUser.Id); existingUsersIdDict.Add(newUser.ExternalId, newUser.Id);
} }
catch(BadRequestException) catch(BadRequestException)
{ {
continue; continue;
} }
} }
var existingUsersEmailsDict = existingUsersOriginal
.Where(u => string.IsNullOrWhiteSpace(u.ExternalId))
.ToDictionary(u => u.Email);
var newUsersEmailsDict = newUsers.ToDictionary(u => u.Email);
var usersToAttach = existingUsersEmailsDict.Keys.Intersect(newUsersEmailsDict.Keys).ToList();
foreach(var user in usersToAttach)
{
var orgUserDetails = existingUsersEmailsDict[user];
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserDetails.Id);
if(orgUser != null)
{
orgUser.ExternalId = newUsersEmailsDict[user].ExternalId;
await _organizationUserRepository.UpsertAsync(orgUser);
existingUsersIdDict.Add(orgUser.ExternalId, orgUser.Id);
}
}
} }
// Groups // Groups
if(groups?.Any() ?? false) if(groups?.Any() ?? false)
{ {
var groupsDict = groups.ToDictionary(g => g.Item1.ExternalId); var groupsDict = groups.ToDictionary(g => g.Group.ExternalId);
var existingGroups = (await _groupRepository.GetManyByOrganizationIdAsync(organizationId)).ToList(); var existingGroups = (await _groupRepository.GetManyByOrganizationIdAsync(organizationId)).ToList();
var existingGroupsDict = existingGroups.ToDictionary(g => g.ExternalId); var existingGroupsDict = existingGroups.ToDictionary(g => g.ExternalId);
var newGroups = groups var newGroups = groups
.Where(g => !existingGroupsDict.ContainsKey(g.Item1.ExternalId)) .Where(g => !existingGroupsDict.ContainsKey(g.Group.ExternalId))
.Select(g => g.Item1); .Select(g => g.Group);
foreach(var group in newGroups) foreach(var group in newGroups)
{ {
group.CreationDate = group.RevisionDate = DateTime.UtcNow; group.CreationDate = group.RevisionDate = DateTime.UtcNow;
await _groupRepository.CreateAsync(group); await _groupRepository.CreateAsync(group);
await UpdateUsersAsync(group, groupsDict[group.ExternalId].Item2, existingUsersIdDict); await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, existingUsersIdDict);
} }
var updateGroups = existingGroups var updateGroups = existingGroups
@ -1003,7 +1027,7 @@ namespace Bit.Core.Services
foreach(var group in updateGroups) foreach(var group in updateGroups)
{ {
var updatedGroup = groupsDict[group.ExternalId].Item1; var updatedGroup = groupsDict[group.ExternalId].Group;
if(group.Name != updatedGroup.Name) if(group.Name != updatedGroup.Name)
{ {
group.RevisionDate = DateTime.UtcNow; group.RevisionDate = DateTime.UtcNow;
@ -1012,7 +1036,7 @@ namespace Bit.Core.Services
await _groupRepository.ReplaceAsync(group); await _groupRepository.ReplaceAsync(group);
} }
await UpdateUsersAsync(group, groupsDict[group.ExternalId].Item2, existingUsersIdDict, await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, existingUsersIdDict,
existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null); existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null);
} }
} }

View File

@ -15,7 +15,7 @@ BEGIN
[dbo].[OrganizationUser] OU [dbo].[OrganizationUser] OU
INNER JOIN INNER JOIN
[dbo].[GroupUser] GU ON GU.[OrganizationUserId] = OU.[Id] [dbo].[GroupUser] GU ON GU.[OrganizationUserId] = OU.[Id]
INNER JOIN LEFT JOIN
[dbo].[User] U ON U.[Id] = OU.[UserId] [dbo].[User] U ON U.[Id] = OU.[UserId]
WHERE WHERE
GU.[GroupId] = @GroupId GU.[GroupId] = @GroupId