From 472a4ade8f0c64946e652047f9af6e951fabcba7 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 28 Mar 2017 21:16:19 -0400 Subject: [PATCH] org invite validation and email tweaks --- .../Repositories/IOrganizationUserRepository.cs | 1 + .../SqlServer/OrganizationUserRepository.cs | 13 +++++++++++++ .../Implementations/OrganizationService.cs | 15 +++++++++++---- .../Implementations/SendGridMailService.cs | 2 ++ src/Sql/Sql.sqlproj | 1 + ...OrganizationUser_ReadByOrganizationIdEmail.sql | 15 +++++++++++++++ 6 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationIdEmail.sql diff --git a/src/Core/Repositories/IOrganizationUserRepository.cs b/src/Core/Repositories/IOrganizationUserRepository.cs index 9601a5ffd7..efb151b172 100644 --- a/src/Core/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/Repositories/IOrganizationUserRepository.cs @@ -10,6 +10,7 @@ namespace Bit.Core.Repositories public interface IOrganizationUserRepository : IRepository { Task GetByOrganizationAsync(Guid organizationId, Guid userId); + Task GetByOrganizationAsync(Guid organizationId, string email); Task>> GetDetailsByIdAsync(Guid id); Task> GetManyDetailsByOrganizationAsync(Guid organizationId); Task> GetManyDetailsByUserAsync(Guid userId, diff --git a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs index 3747a253bc..6c0a2752a9 100644 --- a/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationUserRepository.cs @@ -34,6 +34,19 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task GetByOrganizationAsync(Guid organizationId, string email) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[OrganizationUser_ReadByOrganizationIdEmail]", + new { OrganizationId = organizationId, Email = email }, + commandType: CommandType.StoredProcedure); + + return results.SingleOrDefault(); + } + } + public async Task>> GetDetailsByIdAsync(Guid id) { using(var connection = new SqlConnection(ConnectionString)) diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index b5eb578f85..70171ef9f7 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -105,9 +105,15 @@ namespace Bit.Core.Services throw new BadRequestException("Cannot invite users."); } - // TODO: make sure user is not 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."); + } - // TODO: validate subvaults? + var orgSubvaults = await _subvaultRepository.GetManyByOrganizationIdAsync(organizationId); + var filteredSubvaults = subvaults.Where(s => orgSubvaults.Any(os => os.Id == s.SubvaultId)); var orgUser = new OrganizationUser { @@ -122,7 +128,7 @@ namespace Bit.Core.Services }; await _organizationUserRepository.CreateAsync(orgUser); - await SaveUserSubvaultsAsync(orgUser, subvaults, true); + await SaveUserSubvaultsAsync(orgUser, filteredSubvaults, true); await SendInviteAsync(orgUser); return orgUser; @@ -147,10 +153,11 @@ namespace Bit.Core.Services private async Task SendInviteAsync(OrganizationUser orgUser) { + var org = await _organizationRepository.GetByIdAsync(orgUser.OrganizationId); var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow); var token = _dataProtector.Protect( $"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}"); - await _mailService.SendOrganizationInviteEmailAsync("Organization Name", orgUser, token); + await _mailService.SendOrganizationInviteEmailAsync(org.Name, orgUser, token); } public async Task AcceptUserAsync(Guid organizationUserId, User user, string token) diff --git a/src/Core/Services/Implementations/SendGridMailService.cs b/src/Core/Services/Implementations/SendGridMailService.cs index f972f608c9..0272291d90 100644 --- a/src/Core/Services/Implementations/SendGridMailService.cs +++ b/src/Core/Services/Implementations/SendGridMailService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using SendGrid; using SendGrid.Helpers.Mail; +using System.Net; namespace Bit.Core.Services { @@ -98,6 +99,7 @@ namespace Bit.Core.Services message.AddSubstitution("{{organizationId}}", orgUser.OrganizationId.ToString()); message.AddSubstitution("{{organizationUserId}}", orgUser.Id.ToString()); message.AddSubstitution("{{token}}", token); + message.AddSubstitution("{{email}}", WebUtility.UrlEncode(orgUser.Email)); message.AddCategories(new List { AdministrativeCategoryName, "Organization Invite" }); await _client.SendEmailAsync(message); diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 5e6a09abfa..e244caef65 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -177,5 +177,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationIdEmail.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationIdEmail.sql new file mode 100644 index 0000000000..b9ba7a515f --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationIdEmail.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_ReadByOrganizationIdEmail] + @OrganizationId UNIQUEIDENTIFIER, + @Email NVARCHAR(50) +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationUserView] + WHERE + [OrganizationId] = @OrganizationId + AND [Email] = @Email +END \ No newline at end of file