1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-16 17:15:40 -05:00

bulk invite apis

This commit is contained in:
Kyle Spearrin 2017-05-18 12:04:27 -04:00
parent 413d49f93b
commit c582929daf
4 changed files with 75 additions and 34 deletions

View File

@ -92,7 +92,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.Emails, model.Type.Value,
model.AccessAll, null, model.Collections?.Select(c => c.ToSelectionReadOnly())); model.AccessAll, null, model.Collections?.Select(c => c.ToSelectionReadOnly()));
} }

View File

@ -1,18 +1,41 @@
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System;
using System.Linq;
namespace Bit.Core.Models.Api namespace Bit.Core.Models.Api
{ {
public class OrganizationUserInviteRequestModel public class OrganizationUserInviteRequestModel : IValidatableObject
{ {
[Required] [Required]
[EmailAddress] public IEnumerable<string> Emails { get; set; }
public string Email { get; set; }
[Required] [Required]
public Enums.OrganizationUserType? Type { get; set; } public Enums.OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; } public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(!Emails.Any())
{
yield return new ValidationResult("An email is required.");
}
if(Emails.Count() > 20)
{
yield return new ValidationResult("You can only invite up to 20 users at a time.");
}
var attr = new EmailAddressAttribute();
for(int i = 0; i < Emails.Count(); i++)
{
if(!attr.IsValid(Emails.ElementAt(i)))
{
yield return new ValidationResult($"Email #{i + 1} is not valid.", new string[] { nameof(Emails) });
}
}
}
} }
public class OrganizationUserAcceptRequestModel public class OrganizationUserAcceptRequestModel

View File

@ -23,6 +23,8 @@ namespace Bit.Core.Services
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, string externalId, IEnumerable<SelectionReadOnly> collections); OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid invitingUserId, IEnumerable<string> emails,
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);

View File

@ -685,6 +685,15 @@ 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, string externalId, IEnumerable<SelectionReadOnly> collections) OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
{
var result = await InviteUserAsync(organizationId, invitingUserId, new List<string> { email }, type, accessAll,
externalId, collections);
return result.FirstOrDefault();
}
public async Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid invitingUserId,
IEnumerable<string> emails, 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)
@ -695,45 +704,52 @@ namespace Bit.Core.Services
if(organization.Seats.HasValue) if(organization.Seats.HasValue)
{ {
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organizationId); var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(organizationId);
if(userCount >= organization.Seats.Value) var availableSeats = organization.Seats.Value - userCount;
if(availableSeats >= emails.Count())
{ {
throw new BadRequestException("You have reached the maximum number of users " + throw new BadRequestException("You have reached the maximum number of users " +
$"({organization.Seats.Value}) for this organization."); $"({organization.Seats.Value}) for this organization.");
} }
} }
// Make sure user is not already invited var orgUsers = new List<OrganizationUser>();
var existingOrgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, email); foreach(var email in emails)
if(existingOrgUser != null)
{ {
throw new BadRequestException("User already invited."); // Make sure user is not already invited
var existingOrgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, email);
if(existingOrgUser != null)
{
throw new BadRequestException("User already invited.");
}
var orgUser = new OrganizationUser
{
OrganizationId = organizationId,
UserId = null,
Email = email.ToLowerInvariant(),
Key = null,
Type = type,
Status = OrganizationUserStatusType.Invited,
AccessAll = accessAll,
ExternalId = externalId,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow
};
if(!orgUser.AccessAll && collections.Any())
{
await _organizationUserRepository.CreateAsync(orgUser, collections);
}
else
{
await _organizationUserRepository.CreateAsync(orgUser);
}
await SendInviteAsync(orgUser);
orgUsers.Add(orgUser);
} }
var orgUser = new OrganizationUser return orgUsers;
{
OrganizationId = organizationId,
UserId = null,
Email = email.ToLowerInvariant(),
Key = null,
Type = type,
Status = OrganizationUserStatusType.Invited,
AccessAll = accessAll,
ExternalId = externalId,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow
};
if(!orgUser.AccessAll && collections.Any())
{
await _organizationUserRepository.CreateAsync(orgUser, collections);
}
else
{
await _organizationUserRepository.CreateAsync(orgUser);
}
await SendInviteAsync(orgUser);
return orgUser;
} }
public async Task ResendInviteAsync(Guid organizationId, Guid invitingUserId, Guid organizationUserId) public async Task ResendInviteAsync(Guid organizationId, Guid invitingUserId, Guid organizationUserId)